轉載聲明:Ryan的博客文章歡迎您的轉載,但在轉載的同時,請註明文章的來源出處,不勝感激! :-) java
http://my.oschina.net/ryanhoo/blog/93285 程序員
摘要:在我翻譯的Google官方系列教程中,Bitmap系列由淺入深地介紹瞭如何正確的解碼Bitmap,異步線程操做以及使用Fragments重用等技術,而且在最後給出了很是強大的獨家祕笈:BitmapFun,讓猿媛們得以一窺究竟Google的攻城師們是如何高屋建瓴地秒殺OOM的。 緩存
前言 異步
在下載到BitmapFun.rar這個神聖的壓縮包之後,我是雙手顫抖,彷佛是打開上古祕藏通常,心情激動致使久久不能自已。我還記得那天上海下着小雨,我當時霍然起身,佇立在23樓的窗臺,仰着頭向江水對岸的東方明珠望去,彷佛這樣我鬱積已久的眼淚就不能掉下來。說到這裏,Ryan又暗自抹了一把眼淚。短暫地忘記了過去的黑暗時光,那一個漫長的被OOM的淫威所折磨的盛夏。。。 學習
最後在Boss詫異的目光中,我回到辦公桌,按捺着心裏洶涌的情緒波動,而後當心翼翼的打開BitmapFun.rar。當那些在洪荒時代就活躍在Android平臺的大師們書寫的篇章呈如今我眼前時,個人表情與阿寶從師父手裏獲得Dragon Scroll時通常,永久的定格在了極度天真的期待與眼角一抽一抽的狀態。 測試
那些泛黃的代碼在我看去,通篇只有一句話:老子看不懂! spa
自力更生,構建本身的緩存模塊 .net
Google的這個demo堪稱詳盡,考慮極其周詳,天然是極好的。可是當原理被層層的「特殊狀況」包裝起來,本來簡單的例子變得異常複雜,幾個類之間的關係錯綜複雜,堪比吸血鬼日記幾個帥哥美女之間的關係。要理解清楚每一句代碼的含義,你必定要有理解Matt那人老珠黃的老孃和他和失落的好朋友Taylor搞在一塊兒的覺悟。 線程
好了,吐槽一下就收,千萬不要懷疑Google,人家已經仁至義盡了。BitmapFun中在下載後將Bitmap緩存起來,緩存作了兩份:LruCache和DiskLruCache,分別是內存緩存和硬盤緩存。此外兩個相當重要的類是: 翻譯
BitmapWorkerTask(ImageView imageView) AsyncDrawable extends BitmapDrawable AsyncDrawable(Resources res, Bitmap bitmap,BitmapWorkerTask bitmapWorkerTask)
BitmapWorkerTask持有一個WeakReference<ImageView> imageViewReference,弱引用ImageView,用做異步處理加載圖片的任務。
AsyncDrawable巧妙的引用持有弱引用WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference,是BitmapDrawable的子類,這樣就能夠setImageBitmap(AsyncDrawable)
關係:AsyncDrawable中弱引用BitmapWorkerTask。實際上是圖片引用ImageView的關係,而ImageView.getDrawable又能夠得到圖片。這種高妙的思想不是正值得咱們學習麼?
固然,這節課並非講解官方Demo的,在講解它以前,咱們先來學習一個更加簡單的緩存實現方案,使用最簡單的方式快速構建本身應用的緩存模塊,有效避免OOM異常。它的難度很是小也很方便理解,能夠在這個緩存實現的基礎上,咱們再去理解更加高妙的BitmapFun的緩存實現方案。
後面將要介紹的緩存方案已經應用在一個項目中(該項目將於13年1月20開源,使用Github託管,記念我22歲的生日),效果至關不錯,下載並顯示上百張Bitmap也異常流暢,甚至沒有半點的停頓,全程使用Emulator測試也沒有出現過OOM異常,內存處於可控狀態。
如何解決OOM
Bitmap之因此容易引發OOM異常,緣由已經在Bitmap系列教程中說的明明白白。可是咱們至少清楚一點:一個手機屏幕再大,合理尺寸的Bitmap也不至於耗空全部內存,那要怎麼作才能避免OOM呢?
此外網上也有很多歪門邪道,我我的認爲是不可取的,使用這些簡單粗暴的方法,後期會爲你帶來更大的麻煩:
控制Bitmap的生命週期纔是正解,BitmapFun使用的LruCache是將它將最近被引用到的對象存儲在一個強引用的LinkedHashMap中,而且在緩存超過了指定大小以後將最近不常使用的對象釋放掉。
Memory Cache的Size是受限的,所以加入DiskLruCache,雖然在訪問速度上遜於Memory Cache,可是速度也是至關可觀的。
借鑑Google的作法,我也將緩存作了兩份,一份是Memory Cache,使用弱引用的WeakHashMap來控制Bitmap的生命週期,後面會有詳細解釋。另外一份嚴格來講不能算是緩存,直接將文件存儲在SDCard上,避免重複下載。
佛說引用,既非引用,是名引用。
關於引用,或許對於小菜鳥們不是很好理解(我碰到過太多Java都沒學好來作Android的,基礎很重要!)。我使用金剛經的著名三段論來解釋它:佛說XX,既非XX,是名XX。
這句話什麼意思呢?好比佛說大米,既能夠說它不是大米,只是名字叫作大米罷了。不會由於你爲它更名叫作大麥而改變它的本質,你叫它作水,吃到嘴裏的仍是原來的味道。
關於引用,跟這個有着很是類似的共性。引用就至關於實際對象的名字,好比下面的例子:
Person p1 = new Person(); Person p2 = null; p2 = p1; p1 = null;
new Person()這個對象的名是p1,然後你將名字改爲了p2,對象仍是那個對象,不會由於你將p1的大名蓋在null的頭上而改變它的本質。以上的p1和p2都是引用,它們都不過是名。
在瞭解到引用的含義後,虛擬機會告訴你,被引用的對象處於可得到(reachable)狀態,它是你的好管家,既然你要用它,它就不會回收它。(你想一想若是你正在吃一隻烤鴨,人家忽然一把搶了過去扔垃圾桶了你什麼感受。)
若是在上面的那段程序後面加上p2 = null,Person這個對象就沒有任何引用指向它了,垃圾回收器會在不肯定的時間進行回收。(你都把東西扔了,總不能不讓人家收破爛吧?)
若是你想繼續持有這個對象的引用,但願能夠繼續訪問,可是也容許垃圾回收器進行回收,該怎麼辦呢?(你想減肥,告訴你的好朋友說,若是察覺到你太胖了,就將你嘴裏的烤鴨搶去扔了。如果你很餓,身材也不錯,你要繼續吃。)
這個時候,咱們須要藉助Java提供的軟/弱/虛引用。咱們平時使用的如p1和p2這樣的叫作強引用(Strong Reference)。要使垃圾回收器能在內存不夠的時候,主動搶下你嘴裏的烤鴨,進行回收,須要使用這些:
它們按照由強到弱的引用關係排列,虛引用至關於幾乎沒有引用。文藝青年常說的若即若離用來形容它再恰當不過了。
關於這三個引用的具體學習,詳見我提供的參考資料。這裏只是向你解釋爲何使用弱引用能夠起到防止Bitmap過多而致使內存緊張的做用。
在這裏,因爲我須要使用Bitmap和名字的key-value對應關係,我使用Java提供的WeakHashMap(String key, Bitmap value),顧名思義,它用來保存WeakReference,而且確保每一個key只對應一個值,在內存不夠的時候,垃圾回收器會進行回收。當key值索引不到Bitmap,再進行其餘的操做。
原理示意圖
我將原理畫成圖,以便你們的理解。主體有三個,分別是UI,緩存模塊和數據源。它們之間的關係以下:
① UI:請求數據,使用惟一的Key值索引Memory Cache中的Bitmap。
② 內存緩存:緩存搜索,若是能找到Key值對應的Bitmap,則返回數據。不然執行第三步。
③ 硬盤存儲:使用惟一Key值對應的文件名,檢索SDCard上的文件。
④ 若是有對應文件,使用BitmapFactory.decode*方法,解碼Bitmap並返回數據,同時將數據寫入緩存。若是沒有對應文件,執行第五步。
⑤ 下載圖片:啓動異步線程,從數據源下載數據(Web)。
⑥ 若下載成功,將數據同時寫入硬盤和緩存,並將Bitmap顯示在UI中。
總結:這節課除了吐槽,主要的仍是原理分析。若是你有更好的緩存方案,歡迎提出。下節課將講解具體的Memory Cache和FileCache如何實現。
參考資料:
【1】Thinking In Java 4th Chapter 17.12 Hoding References.pdf http://vdisk.weibo.com/s/jtqjr
【2】李剛:突破程序員基本功的16課之Java的內存回收.pdf http://vdisk.weibo.com/s/jtqik