介紹
在Android開發中Bitmap肯定是繞不過去的,很多時候我們只是使用圖片框架載入圖片,具體細節的Bitmap對記憶體的使用圖片框架已經幫我們封裝好了。但是對Btimap對記憶體的影響我們還是需要了解的。
記憶體佔用
首先要清楚Bitmap的檔案大小肯定不是實際的記憶體載入大小。因為檔案只是儲存的資訊,載入到記憶體中顯示出來時還需要經過轉換。
獲取執行的時的記憶體佔用:
針對Bitmap點陣圖物件,Android的系統框架中的graphics包下的BItmap類。有bitmap.getByteCount()
方法獲取記憶體大小,單位位元組(byte)
其實本質上Bitmap的記憶體佔用計算非常簡單:
基本公式:總記憶體=寬×高×色彩空間
但是在實際執行中不是這麼簡單,公式的每個引數都會有被不同因素影響。
影響記憶體大小的三要素
我們這裡不從程式碼出發,直接說重點不會雲山霧罩的。
以前文的公式為基礎。
影響記憶體大小的三要素
- 圖片寬高。
- 色彩空間
- 縮放比
下面一個個說明
圖片寬高
我們在AS開啟圖片看到的檔案資訊
其中的1000×447就是圖片寬高,或者說是原始寬高。
作用:寬高的乘積描述圖片的畫素點總數,也就是這個圖片由多少畫素構成。
它一般不是我們最終載入到記憶體時的圖片寬高,但是可以認為是基礎變數。
色彩空間
即Bitmap.Config列舉:
作用:色彩空間描述每個畫素點的資訊
ARGB_8888:
總共32位(4byte),分別對應4個數值,數值單位為8bit位=1byte位元組,分別描述透明度(1個)+RGB通道(3個)。每個位元組數值範圍0-255。作為Bitmap配置色彩空間的預設值。BitmapFactory載入時預設。
public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888
RGB_565:
總共16位(2byte),分別對應3個數值,5位(紅)+6位(綠)+5位(藍)分別描述RGB通道。Glide載入時預設使用,DecodeFormat類
public static final DecodeFormat DEFAULT = PREFER_RGB_565
可以看到,RGB_565只需要ARGB_8888的一半大小,代價是沒有透明度描述。
縮放比
作用:對圖片原始寬高的縮放設定認定為縮放比。
首先需要指出,縮放比在記憶體的影響上是次方的,影響值=縮放比×縮放比。這是因為縮放比分別作用在寬高上,使用了兩次。
縮放比會被很多因素影響。
主動設定:
在解析Bitmap時,有個可選的Options物件,其中inSampleSize
引數可以影響縮放比的結果。當使用該引數值時要求大於1且是2的倍數,比如在inSampleSize=2
時,縮放比被縮小2倍(該功能只有縮小沒有放大的可能),即“縮放比=原始縮放比×(1/2)”。對記憶體結果的影響是縮小4倍,因為寬/高都被縮小2倍。該值預設不生效,需要手動設定。
被動設定
在系統中主要由,具體執行裝置dpi的和圖片檔案儲存的drawable檔案dpi層級決定。
首先要引出螢幕密度概念。這是Android為應對眾多的不同的螢幕解析度色裝置提出的概念和單位。
- drawable檔案的dpi層級:在Drawable系列檔案中儲存的Bitmap點陣圖檔案。根據Android開發的規範,Drawable的系列檔案中的修飾符後命名是有意義的,宣告瞭這個檔案所屬的Dpi(螢幕密度)層級。檔案眾多也對應了眾多的Android裝置螢幕密度。
- 裝置的dpi層級:參考https://material.io/devices/可以瞭解。
裝置和drawable檔案的dpi縮放比計算:
比如drawable-xhdpi(320=2160=2mdpi)下的bitmap被載入到xxxhdpi(640=4160=4mdpi)的Pixel-XL中。
dpi縮放比=裝置dpi/drawable的dpi,所以上面的,dpi縮放比=640/320=2。
實際意義就是在高解析度的xxxhdpi裝置中,drawable-xhdpi檔案需要放大2倍,即寬高各放大2倍來適應高密度裝置。需要指出的是如果Bitmap檔案儲存在drawable沒有字尾的檔案中,系統會使用drawable的dpi預設層級就是160;
需要說明的是:這個縮放比只在當Bitmap檔案位於drawable這樣有dpi層級的檔案中時生效。如果Bitmap點陣圖檔案位於assets包這樣的外部檔案或者是URL網路地址,是有沒有縮放比的(即預設為1)不影響記憶體結果。這個也好理解,當讀取檔案時得不到其他的資訊就不需要再處理了。有趣的是Glide也是存在縮放比概念的,但是和dpi無關,和View檢視有關。
Glide縮放比=View檢視值/原圖值 ,這裡的值是不固定的,因為縮放比只有一個,但是檢視寬高和原圖寬高的比值有兩個,需要權衡取出能夠同時應用於寬高的縮放比。
最終縮放比
最後的縮放比結果:
縮放比=主動設定縮放比*被動設定縮放比
主動縮放比:沒有設定時,預設為1,即不影響另外的值。
被動縮放比:這需要看能不能得到裝置和drawable的關係(或者其他關係,如Glide的檢視圖片比例)如果不能得到也是1。
最終公式
基於以上認識,豐富上文的基本公式,可以的得到最終的計算Bitmap記憶體公式
最終公式:總記憶體=(原始寬×縮放比)×(原始高×縮放比)×色彩空間
驗證
原圖:1000寬X447高,位於drawable-xxhdpi(480dpi=3160dpi)檔案包,裝置Pixel-XL(560dpi=3.5160dpi)。主動設定inSampleSize=2。使用預設Bitmap.Config=ARGB_8888
- 縮放比=主動設定×被動設定(dpi層級關係)=1/2×(560/480)=0.5×1.166=0.5833
- 色彩空間=ARGB_8888=32bit=4byte
- 原始大小=1000×447
記憶體佔用=(原始寬×縮放比)×(原始高×縮放比)×色彩空間
=1000×0.5833×447×0.5833×4
=583×260×4
=606320byte
≈0.578MB
最後上圖驗證:
計算結果完美匹配執行結果,這裡說明為什麼故意省略小數點3位以後,這是對應native層程式碼的float小數點結果轉int時的捨棄。
啟示
理解Bitmap的最終記憶體佔用計算原理和記憶體佔用各個引數,我們對Bitmap的處理時就有具體的目標。比如常見的優化Bitmap載入過程,其實就是對Bitmap載入時的各個變數引數設定修改。
常見的Bitmap優化:
- 修改主動縮放比:目標是修改最終圖片載入的寬高,進而優化記憶體佔用。具體就是設定inSampleSize值,如在適當的View上縮放顯示適合的bitmap,實現bitmap的高效載入(Glide圖片框架就是這樣,讓顯示元件View的寬高的參與縮放比計算)。
- 修改被動縮放比:理解了被動縮放比的計算,裝置dpi層級和圖片dpi層級關係。最好的情況就是同樣的圖片在drawable檔案不同的dpi層級包中都有適應具體大小的圖片(這樣在執行不同裝置時,被動縮放比都是1)。否則在裝置沒有直接對應的dpi的drawable檔案時,會影響被動縮放比,而且大多數時候是導致圖片放大,且導致記憶體增大二次方。如剛才的計算過程如果把圖片從drawable-xxhdpi(480dpi)移到drawable檔案包(預設160)中,同樣的載入程式碼最後記憶體就會放大9倍。
- 修改色彩空間:在明確的不需要透明度的情況,使用RGB_565替換ARGB_8888,可以直接達到記憶體縮小一半的功效。缺點就是有限定條件。(從網路上的博文描述,不推薦使用ARGB_4444替換,因為這樣的圖片質量太差)