1. 程式人生 > >通過三次優化,我將gif載入優化了16.9%

通過三次優化,我將gif載入優化了16.9%

WeTest 導讀

現在app越來越炫,動不動就搞點動畫,複雜的動畫用原生實現起來挺複雜,如是就搞起gif播放動畫的形式,節省開發成本。

背 景

設計同學準備給一個png序列,開發讀取png序列,一幀一幀的播放出來,實現一個動畫的效果。

為什麼不直接使用gif,github上有好的開源庫可以直接播放gif的,為嘛?大部分原因還是要回答,專案需求決定。

實現思路:

1、比較偷懶的方式,將設計同學給的png序列直接放到一個 animation-list中,就像這樣子:

這裡寫圖片描述

然後直接,放在設定為一個ImageView就可以了
這裡寫圖片描述

那麼,真的就可以了嗎?答案是,可以,也不可以,因此最終不可以~~(有點繞。。。)

設計同學給了一個90多張png的序列,於是oom的發生,真是悲劇啊,這麼簡單的方案,結果卻是這麼華麗的被拋棄了。

2、使用一個執行緒來讀取PNG序列,另外一個執行緒去播放讀取出來的PNG序列,那麼有一些問題我們要去面對:

a、一個執行緒來讀,一個執行緒寫,讀PNG的執行緒寫,播PNG的執行緒讀,哎呀,有點拗口~~,不過很顯然,這是一個《生產者-消費者模型》,那麼問題是使用什麼存放讀取好的bitmap呢,使用BlockingQueue 吧,為什麼要使用BlockingQueue,如果不懂,請點選這裡,還能不能使用別的,當然,有,而且還不止一個,感興趣可以去這個包下java.util.concurrent探索下。

b、不是怕OOM嗎?那麼,這個方案是否可以解決OOM呢?但是顯然是肯定的了。為什麼這麼說,都到了這種粒度了,OOM當然是可以解決。

b1、首先,我們可以拿到當前的最大記憶體Runtime.getRuntime().maxMemory(),和當前的可用記憶體Runtime.getRuntime().freeMemory();

因此,結合BitmapFactory.Options,的這個inJustDecodeBounds屬性,你完全可以判斷是否還有足夠的記憶體載入更多的bitmap。

b2、其次,維護一個currentSize,記錄解析到記憶體測bitmap佔用的記憶體,每讀一張,currentSize+讀出來的bitmap佔用的記憶體,currentSize顯然是變動的,播放完的bitmap請補上一刀,currentSize - 剛剛播放完的bitmap。

那麼,整個過程似乎可以用這個圖來清晰的表達了:
這裡寫圖片描述

以為這樣就結束了,那你就TOO YOUNG TO SIMPLE 了,是否還能優化?你猜應該是可以吧!

我猜也是可以的,不難發現消費者的消費能力實在太強,讀取PNG的執行緒太不給力,讀的太慢了,播放總是等待讀新的bitmap出來已供展示。那麼?腫麼辦?

多個執行緒去讀啊!

嗯,似乎可以改進成這樣,對嗎?
這裡寫圖片描述

這裡,可能有多個讀取PNG的執行緒,一旦引入了多執行緒,你就會體會到問題會變得複雜多了!

這裡,你要控制,當前讀取進度到了哪裡,因為是多執行緒,所以,你之間那個簡單的int currentLoad 已經不能用了,否則,三個執行緒讀同一張png可能會被你不巧碰到,那麼怎麼辦,使用AtomicInteger,OK,這個問題好像被你解決了,此時,你保證了,所有png被不重複載入完畢!

然而,一個更加頭疼的問題還要你去面對,注意,gif是有播放順序的,然而,你把BlockingQuene做成了這麼一個序列:
這裡寫圖片描述

同學,這樣好嗎?顯然不能接受。那麼,如何保證塞入到BlockingQuene中的bitmap是按照png序列的順序呢?

很顯然要做到這一點,就需要將png的序號帶入到讀取執行緒中。讀取執行緒讀取完畢之後,去問一個manger,大哥,有比我小的讀取執行緒還沒有提交他拿到的bitmap嗎?大哥告訴你還有,那對不起,你乖乖等一會吧,wait(關鍵字),對麼?如果大哥告訴你沒有,你丫就是序號最小的那個哦,那你就把bitmap交給BlockingQuene吧,然而自己就完成光榮使命了。

可問題是,如果你在wait,誰來叫醒你呢?大哥說,他來notify,大哥收到最小的序號的提交的bitmap,等等,(上面說錯了,最小的需要把bitmap交給大哥來提交,),將bitmap交給BlockingQuene,然後大哥此時通知所有讀取執行緒的小弟們,大夥趕緊來交作業了,如是此時你單身10年的左手終於搶到了“鎖”,如是,你把你的作業bitmap交給了大哥了。

圖,我就不畫了,腦補也能補出來,不是嗎?

不滿足鎖,可以優化成無鎖,大哥可以維護一個序列,1對應的座位只能1提交過來,2對應的只能2提過來,維護一個已交給BlockingQuene位置的遊標,有好多種情況,我們用綠色的代表已交給大哥的任務好嗎?

如是,這種情況表達0123已經提交給BlockingQuene,5先完成了,然後3完成了,4沒完成,此時大哥會吧3提交給BlockingQuene對嗎?顯然是,情況還有很多,,可以自己腦補一下,總之,這麼做,讀取執行緒只要讀取完畢,把作業交給大哥就好,不用等待大哥說你是最小的,才讓你提交,是嗎?

這裡寫圖片描述

這樣就OK了嗎?

如果說是,那你還是TOO YOUNG TOO SIMPLE!

萬萬沒想到,之前單個執行緒讀的時候,載入一張PNG耗時才220ms左右,(測試使用模擬器),真機華為mate8略快。
這裡寫圖片描述

然而,使用多執行緒讀的時候,載入一張PNG居然耗時1100ms左右,開了4個讀執行緒。。,真是醉了。
這裡寫圖片描述

執行緒開的有點多?那個2個試試???400ms左右!!!
這裡寫圖片描述

OH,no,回過頭來想想,其實,瓶頸在讀沒有錯,但是讀的瓶頸在手機儲存卡上。。。或許還有其他因素。

3、不死心,繼續思考,單個執行緒讀取png的情況下,是否有可能提高讀取效率?

先把問題放一放,假如真的找不到好的辦法,至少要保證記憶體佔用方面,流暢性方面先,看下記憶體圖譜吧,不看不要緊,一看,就醉了:
這裡寫圖片描述

細心的同學應該看到了鋸齒了,這GC,太酸爽了吧,分析一下,我們沒播放完一幀,就將bitmap給回收了(recycle)了。結果就導致了這種圖的出現,但是又不能不recycler掉,隨著bitmap記憶體佔用不斷增加,OOM勢必難以避免。

那麼,既然釋放也不是,不釋放也不是,那麼,可以不可以將這個要釋放的bitmap繼續拿過來用呢?

什麼意思?

如果要釋放的bitmap的那塊記憶體,能夠直接用來載入新的png,那該多好啊,那麼,是否有這個可能呢?問了下google,他給了我這麼一個答案:

這裡寫圖片描述

options有這麼一個引數 ,可以重用一個bitmap的記憶體去存放解析出另外一個新的bitmap,但是有一定的要求:

4.4以上,只需要old bitmap位元組數比將要載入的bitmap所需的位元組數大,但是低於4.4,要滿足和待載入bitmap長寬畫素一致即可 (更加苛刻)。

而我們的png序列,每張圖片都是一樣大小,顯然,符合這個所有特性(長寬一致)。

如是,有多了集合去儲存即將釋放的bitmap,用來重用。
這裡寫圖片描述

測試一下:

鋸齒果斷消失了,而且,似乎還得到一個額外的獎勵!

這裡寫圖片描述

載入速度提升了
這裡寫圖片描述

分析,可能是因為bitmap記憶體的重用,使得載入新bitmap的時候不用重新分配記憶體,節省了一定的時間。

最後看看絲絲順滑的效果吧
這裡寫圖片描述

針對手遊的效能優化,騰訊WeTest平臺的Cube工具提供了基本所有相關指標的檢測,為手遊進行最高效和準確的測試服務,不斷改善玩家的體驗。目前功能還在免費開放中。歡迎立即體驗!

如果對使用當中有任何疑問,歡迎聯絡騰訊WeTest企業qq:800024531