由于Bitmap的特殊性以及Android對單個應用所施加的記憶體限制,比如16M,這導緻加載Bitmap的時候很容易出現記憶體溢出。比如以下場景:
Android中常用的緩存政策也是很有意思,緩存政策一個通用的思想,可以用到很多場景中,比如在實際開發中經常需要用到Bitmap做緩存。通過緩存政策,我們不需要每次都從網絡上請求圖檔或者從儲存設備中加載圖檔,這樣就極大地提高了圖檔的加載效率以及産品的使用者體驗。目前比較常用的緩存政策是LruCache和DiskLruCache,其中LruCache常被用做記憶體緩存,而DiskLruCache用做存儲緩存。Lru是Least Recently Used的縮寫,即最近最少使用算法,這種算法的核心思想:當緩存快滿時,會淘汰近期最少使用的緩存目标,很顯然Lru算法的思想是很容易被接受的。
Bitmap在Android中指的是一張圖檔,可以是png格式也可以是jpg等其他常見的圖檔格式。BitmapFactory類提供了四類方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分别用于支援從檔案系統、資源、輸入流以及位元組數組中加載出一個Bitmap對象,其中decodeFile和decodeResource又間接調用了decodeStream方法,這四類方法最終是在Android的底層實作的,對應着BitmapFactory類的幾個native方法。
如何高效地加載Bitmap呢,其實核心思想也簡單,那就是采用BitmapFactory.Options來加載所需尺寸的圖檔。主要是用到它的inSampleSize參數,即采樣率。當inSampleSize為1時,采樣後的圖檔大小為圖檔的原始大小,當inSampleSize大于1時,比如為2,那麼采樣後的圖檔其寬/寬均為原圖大小的1/2,而像素數為原圖的1/4,其占有的記憶體大小也為原圖的1/4。從最新官方文檔中指出,inSampleSize的取值應該是2的指數,比如1、2、4、8、16等等。
通過采樣率即可有效地加載圖檔,那麼到底如何擷取采樣率呢,擷取采樣率也很簡單,循序如下流程:
将BitmapFactory.Options的inJustDecodeBounds參數設為True并加載圖檔
從BitmapFactory.Options中取出圖檔的原始寬高資訊,他們對應于outWidth和outHeight參數
根據采樣率的規則并結合目标View的所需大小計算出采樣率inSampleSize
将BitmapFactory.Options的inJustDecodeBounds參數設為False,然後重新加載圖檔。
經過上面4個步驟,加載出的圖檔就是最終縮放後的圖檔,當然也有可能不需要縮放。代碼如下:
緩存政策在Android中有着廣泛的使用場景,尤其在圖檔加載這個場景下,緩存政策變得更為重要。有一個場景就是批量下載下傳網絡圖檔,在PC上是可以把所有的圖檔下載下傳到本地再顯示即可,但是放到移動裝置上就不一樣了。不管是Android還是IOS裝置,流量對于使用者來說都是一種寶貴的資源。
如何避免過多的流量消耗呢,那就是緩存。當程式第一次從網絡加載圖檔後,就将其緩存到儲存設備上,這樣下次使用這張圖檔就不用從網絡上擷取了,這樣就為使用者節省了流量。很多時候為了提高使用者的使用者體驗,往往還會把圖檔在記憶體中再緩存一份,這樣當應用打算從網絡上請求一張圖檔時,程式首先從記憶體中去擷取,如果記憶體中沒有那就從儲存設備中去擷取,如果儲存設備中也沒有,那就從網絡上下載下傳這張圖檔。因為從記憶體中加載圖檔比從儲存設備中加載圖檔要快,是以這樣既提高了程式的效率又為使用者節約了不必要的流量開銷。
目前常用的一種緩存算法是LRU(Least Recently Used),LRU是近期最少使用算法,它的核心思想是當緩存滿時,會優先淘汰那些近期最少使用的緩存對象。采用LRU算法的緩存有兩種:LruCache和DiskLruCache,LruCache用于實作記憶體緩存,而DiskLruCache則充當了儲存設備緩存,通過這二者的完美結合,就可以很友善地實作一個具有很高實用價值的ImageLoader。
LruCache
LruCache是Android 3.1提供的一個緩存類,通過support-v4相容包可以相容到早期的Android版本。它是一個泛型類,它内部采用一個LinkedHashMap,當強引用的方式存儲外界的緩存對象,其提供了get和put方法來完成緩存的擷取和添加操作,當緩存滿時,LruCache會移除較早使用的緩存對象,然後再添加新的緩存對象。
強引用:直接的對象引用
軟引用:當一個對象隻有軟引用存在時,系統記憶體不足時此對象會被gc回收。
弱引用:當一個對象隻有弱引用存在時,此對象會随時被gc回收。
LruCache是線程安全的,因為用到了LinkedHashMap。從Android 3.1開始,LruCache就已經是Android源碼的一部分。
DiskLruCache
DiskLruCache用于實作儲存設備緩存,即磁盤存儲,它通過将緩存對象寫入檔案系統進而實作緩存的效果。DiskLruCache得到了Android官方文檔的推薦,但它不屬于Android SDK的一部分。
ImageLoader的實作
一般來說,一個優秀的ImageLoader應該具備如下功能:
圖檔的同步加載
圖檔的異步加載
圖檔壓縮
記憶體緩存
磁盤緩存
網絡拉取
圖檔的同步加載是指能夠以同步的方式向調用者提供所加載的圖檔,這個圖檔可能是從記憶體緩存讀取的,也可能是從磁盤緩存中讀取的,還可能是從網絡拉取的。
圖檔的異步加載是一個很有用的功能,很多時候調用者不想再單獨的線程中以同步的方式來擷取圖檔,這個時候ImageLoader内部需要自己線上程中加載圖檔并将圖檔設定所需的ImageView。圖檔壓縮的作用更需要了,這是降低OOM機率的有效手段,ImageLoader必須合适地處理圖檔的壓縮問題。
記憶體緩存和磁盤緩存是ImageLoader的核心,也是ImageLoader的意義所在,通過這兩級緩存極大地提高了程式的效率并且有效地降低了對使用者所造成的流量消耗,隻有當這兩級緩存都不可用時才需要從網絡中拉取圖檔。
一個實作ImageLoader的例子:
在一般ListView或者GridView中,使用照片牆的時候,容易出現滑動卡頓,如何優化呢,有三點建議:
不要在getView中執行耗時操作。比如加載圖檔,肯定會導緻卡頓,因為加載圖檔是一個耗時的操作,這種操作必須通過異步的方式來處理。
控制異步任務的執行頻率。比如在異步加載圖檔時,使用者刻意地頻繁上下滑動,這就會在一瞬間産生上百個異步任務,這些異步任務會造成線程池的擁堵并随即帶來大量的UI更新操作,這是沒有意義的。那該如何解決呢,可以考慮在清單滑動的時候,停止加載圖檔,盡管這個過程是異步的,等清單停下來以後在加載圖檔仍然可以獲得良好的使用者體驗。
開啟硬體加速可以解決莫名的卡頓問題,通過設定android:hardwareAccelerated = "true"即可為Activity開啟硬體加速。
源于對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中可以看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關系
4,Android圖檔加載庫了解
5,談談Android運作時權限了解
6,EventBus初了解
7,Android 常見工具類
8,對于Fragment的一些了解
9,Android 四大元件之 " Activity "
10,Android 四大元件之" Service "
11,Android 四大元件之“ BroadcastReceiver "
12,Android 四大元件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的了解
15,Android 生命周期和啟動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工作原理
19,了解 Window 和 WindowManager
20,Activity 啟動過程分析
21,Service 啟動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 架構的一些了解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試