WeTest 導讀html
背 景java
設計同窗準備給一個png序列,開發讀取png序列,一幀一幀的播放出來,實現一個動畫的效果。android
爲何不直接使用gif,github上有好的開源庫能夠直接播放gif的,爲嘛?大部分緣由仍是要回答,項目需求決定。git
實現思路:github
一、比較偷懶的方式,將設計同窗給的png序列直接放到一個 animation-list中,就像這樣子:性能優化
而後直接,放在設置爲一個ImageView就能夠了多線程
那麼,真的就能夠了嗎?答案是,能夠,也不能夠,所以最終不能夠~~(有點繞。。。)app
設計同窗給了一個90多張png的序列,因而oom的發生,真是悲劇啊,這麼簡單的方案,結果倒是這麼華麗的被拋棄了。工具
二、使用一個線程來讀取PNG序列,另一個線程去播放讀取出來的PNG序列,那麼有一些問題咱們要去面對:性能
a、一個線程來讀,一個線程寫,讀PNG的線程寫,播PNG的線程讀,哎呀,有點拗口~~,不過很顯然,這是一個《生產者-消費者模型》,那麼問題是使用什麼存放讀取好的bitmap呢,使用BlockingQueue 吧,爲何要使用BlockingQueue,若是不懂,請點擊這裏,還能不能使用別的,固然,有,並且還不止一個,感興趣能夠去這個包下java.util.concurrent探索下。
b、不是怕OOM嗎?那麼,這個方案是否能夠解決OOM呢?可是顯然是確定的了。爲何這麼說,都到了這種粒度了,OOM固然是能夠解決。
b一、首先,咱們能夠拿到當前的最大內存Runtime.getRuntime().maxMemory(),和當前的可用內存Runtime.getRuntime().freeMemory();
所以,結合BitmapFactory.Options,的這個inJustDecodeBounds屬性,你徹底能夠判斷是否還有足夠的內存加載更多的bitmap。
b二、其次,維護一個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,回過頭來想一想,其實,瓶頸在讀沒有錯,可是讀的瓶頸在手機存儲卡上。。。或許還有其餘因素。
三、不死心,繼續思考,單個線程讀取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/help/documentation/10096.html
若是對使用當中有任何疑問,歡迎聯繫騰訊WeTest企業qq:800024531