通過三次優化,我將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,他給了我這麼一個答案:
https://developer.android.com/training/displaying-bitmaps/manage-memory.html#recycle
options有這麼一個參數 ,可以重用一個bitmap的內存去存放解析出另外一個新的bitmap,但是有一定的要求:
4.4以上,只需要old bitmap位元組數比將要載入的bitmap所需的位元組數大,但是低於4.4,要滿足和待載入bitmap長寬像素一致即可 (更加苛刻)。
而我們的png序列,每張圖片都是一樣大小,顯然,符合這個所有特性(長寬一致)。
如是,有多了集合去存儲即將釋放的bitmap,用來重用。
測試一下:
鋸齒果斷消失了,而且,似乎還得到一個額外的獎勵!
載入速度提升了
分析,可能是因為bitmap內存的重用,使得載入新bitmap的時候不用重新分配內存,節省了一定的時間。
最後看看絲絲順滑的效果吧
針對手游的性能優化,騰訊WeTest平台的Cube工具提供了基本所有相關指標的檢測,為手游進行最高效和準確的測試服務,不斷改善玩家的體驗。目前功能還在免費開放中。歡迎立即體驗!
體驗地址:http://wetest.qq.com/product/cube幫助中心:http://wetest.qq.com/help/documentation/10096.html
如果對使用當中有任何疑問,歡迎聯繫騰訊WeTest企業qq:800024531
推薦閱讀:
※如何精簡Unity中使用的字體文件
※Unity載入模塊深度解析(Shader)
※Linux性能優化12:網路IO的調度模型
※利用Unity UGUI製作酷炫UI效果(製作篇)
TAG:性能优化 |