Bitmap的記憶體
在我們的日常開發中,不免經常與Bitmap打交道。在為我們渲染美麗的介面的同時,也經常會帶給我們許多的苦惱。比如你會發現,Bitmap佔用的記憶體通常很大,很多OOM的原因都是由於Bitmap佔用的記憶體過大導致的。那麼到底為什麼Bitmap會佔用很大的記憶體,而我們又該怎麼做來避免呢?下面我們來簡單的瞭解下Bitmap。
如何計算Bitmap的記憶體

1.png
我們先來看上面這張圖。這張圖是我用模擬器跑的一個demo,然後dump的一份hprof檔案。這裡打了個馬賽克,讓我們先暫且忽略它,後面再為大家揭曉。可以先告訴大家的是,這張圖片的尺寸是700 * 1050。我們看到dump的資訊中有一個變數叫做mBuffer,簡單介紹一下。mBuffer就是一張bitmap開在java層的的記憶體的大小,你可以暫時簡單理解成一張圖片所佔用的記憶體大小,為什麼說暫時呢,因為其實應該是大於等於的,這設計到記憶體複用的邏輯。不過這不是我們這次分享的重點,暫且把它當作一張bitmap所佔用的記憶體。放眼一望,可以看到這張圖片佔用了很大的記憶體,6615000位元組,算了一下,也就是 700 * 1050 * 9,是我們剛才看到的圖片尺寸的九倍!所以這個九倍是怎麼來的呢?
我們瞭解到,每張圖片都有自己不同的畫素格式。Android支援四種格式,分別是ARGB_8888,ARGB_4444,RGB_565和APLHA_8。每個畫素格式的區別如下表:
畫素格式 | 單位畫素位元組數 | 備註 |
---|---|---|
ARGB_8888 | 4 | ARGB每個通道佔8位,即每個畫素點佔32位 |
ARGB_4444 | 2 | 每個通道佔4位,即每個畫素點佔16位 |
RGB_565 | 2 | RGB三個通道分別佔5、6、6位,即每個畫素點佔16位 |
ALPHA_8 | 1 | 僅alpha通道,佔8位,即每個畫素點佔8位 |
每個原色都儲存著所表示的顏色值,即點陣圖數越高,所儲存的顏色也就越豐富,也就是我們說的畫質越高。所以我們認為,一張圖片所佔用的記憶體是由所有畫素點所佔有的位元組大小決定的。即:
一張圖片的記憶體 = 寬 * 高 * 單位畫素位元組數。
其中ARGB_4444已經在API 19及以上的版本廢棄了,預設使用32位點陣圖,而ALPHA_8最為特殊,適用場景不多,所以對於我們來說,最常用的就是ARGB_8888和RGB_565兩種格式。按照我們的推算公式,ARGB_8888格式的圖片應該會比RGB_565格式的圖片記憶體大兩倍。下面我們來實驗下。

2.png
圖中我們使用上述兩種圖片格式去載入了同一張圖片(預設採用ARGB_8888格式),我們從log資訊中可以清楚的看到,確實是程兩倍關係。但是一定是兩倍嗎?
顯然答案是否定的。不然我也就不會問這種問題了= =。我們來看下圖:

3.png
先從結果上看,這次雖然採用的還是兩種格式,但是加載出來的圖片大小卻是一樣的。不知道有沒有細心的小夥伴可以一眼看出這張圖和上一張圖的區別(不是image1變成image2了。。。),我們發現,這次載入的一張圖片是png格式的。我們都知道,png格式是有透明度的圖片,那麼為什麼使用一張png格式的圖片。我們給圖片設定的畫素格式就沒用了呢?

4.png
再看下我們剛才給圖片設定畫素格式的引數,“inPreferredConfig”。再結合下這個引數的註釋,不難理解,這只是個“建議”。但是如果系統發現這張圖片不匹配,比如我們上面的例子,他會自己選擇一個最合適的畫素格式來載入圖片,也就說明了,並不是我們設定了畫素格式就會有用的。
回到我們一開始的問題,我們得到的答案是9倍的關係,但是即使我們使用預設的ARGB_8888的畫素格式,也只有4倍呀,那剩下的幾倍在哪裡呢?

5.png
回到我們最開始的那張圖,我們把馬賽克去掉,發現並不是我告訴你們的700 * 1050的尺寸。但是我並沒有騙你們!那麼為什麼寬和高都被放大了呢?其實這個是因為有一個情況我沒有告訴你們。上面測試的時候可以看到我把圖片放在了xhdpi目錄下,其實,我使用的模擬器試xxhdpi的,也就是480dpi,是xhdpi的1.5倍,系統會根據自己的位深去對應的目錄去找資源,如果找不到的話會從其他的目錄裡找。我們放到xhdpi目錄下的資源系統會認為那是屬於xhdpi位深的,所以使用在xxhdpi下就會對其進行擴大。也就導致了寬和高分別被擴大了 480 / 320 即1.5倍,至此終於湊齊了我們開篇所拋下的問題,為什麼是9倍的關係了(1.5 * 1.5 * 4)。
關於踩坑
現在我們瞭解到了一張bitmap可能會為我們帶來如此巨大的記憶體開銷,而在日常開發過程中,經常有人跟我們滲透“使用完bitmap要及時釋放”的邏輯。我深深的記在心裡,於是寫出了下面的程式碼:

6.png
放眼望去可能沒什麼問題,先載入了一張圖片A,然後通過一些變換得到了另外一張圖片B,然後我及時的把A釋放掉了!(快誇我!)
然而並沒有如我們所願,我在跑demo的時候崩潰了,於是我們看了下原始碼,找到了下面這一段。。。

7.png
我們可以看到,這裡寫著如果返回的bitmap與原來的一樣的話,就會把我們傳進去的A物件返回出來!所以其實我們上面的做法是不保險的,也就是存在A和B是同一個物件的可能!
再來看下面一段類似的程式碼:

8.png
因為踩過上一個坑,這次我機智的改變了他的高度,也就是不會返回給我同一個物件了。但是這次我換了取bitmap的方法,這樣寫有問題嗎?當然有問題了,不然我拿出來幹嘛。不過奇怪的是,我們這次第一次跑這段程式碼的時候是沒有異常的,但是在第二次發生了崩潰!老話說得好,有問題,翻原始碼,機智的我又找到了下面這段程式碼:

9.png
恍然大悟,原來我們獲取資源中的bitmap的時候,系統會為我們做一個快取,也就是第二次開始所取出來的圖片都是我們之前快取的,而經過了我們第一次的釋放後,快取中的bitmap被釋放掉了,也就不難解釋為什麼我們第二次取出來的bitmap再使用的時候會崩潰了。既然做了快取,那麼其實如果我們反覆取這張圖,記憶體是不會增長的,而使用BitmapFactory的方法記憶體是會一直增長的,有興趣的小夥伴可以試一下。
至此這次分享的內容結束了,主要是想帶大家來了解一下bitmap的一些知識,避免由於bitmap所帶來一些記憶體上的問題,以後大家在處理記憶體優化的時候可以更加小心。