天天看點

Bitmap.recycle引發的血案從Bitmap.recycle說起然而……

在Android中,Bitmap的存儲分為兩部分,一部分是Bitmap的資料,一部分是Bitmap的引用。

在Android2.3時代,Bitmap的引用是放在堆中的,而Bitmap的資料部分是放在棧中的,需要使用者調用recycle方法手動進行記憶體回收,而在Android2.3之後,整個Bitmap,包括資料和引用,都放在了堆中,這樣,整個Bitmap的回收就全部交給GC了,這個recycle方法就再也不需要使用了。

現在的SDK中對recycle方法是這樣注釋的,如圖所示:

Bitmap.recycle引發的血案從Bitmap.recycle說起然而……

可以發現,系統建議你不要手動去調用,而是讓GC來進行處理不再使用的Bitmap。我們可以認為,即使在Android2.3之後的版本中去調用recycle,系統也是會強制回收記憶體的,隻是系統不建議這樣做而已。

鄙司代碼有些是從Android2.3出來的,是以很多地方還在使用Bitmap.recycle。通常情況下,這也沒什麼問題,但是,今天遇到一個bug引發了Bitmap.recycle的血案。

這個bug的起因是因為我們的一張圖檔需要旋轉,同時可以設定一個旋轉角度,老的代碼是這樣寫的:

除了中間的0.013558723994643297f這串比較奇葩的資料(當然,正常情況下都是20、30這樣正常的數),其它都是比較正常的代碼。

但實際上,隻要一運作這段代碼,程式就會崩潰,錯誤原因如下所示:

這個問題一看就知道是由于Bitmap被調用recycle方法回收後,又調用了Bitmap的一些方法而導緻的。可是,代碼中可以發現我們recycle的是bitmap而不是通過Bitmap.createBitmap重新生成的targetBmp,為什麼會報這個exception呢?

按道理來說,bitmap與create出來的targetBmp應該是兩個對象,當旋轉角度正常的時候,确實也是這樣,但當旋轉角度比較奇葩的時候,這兩個bitmap對象居然變成了同一個!而打開Bitmap.createBitmap的代碼,可以發現如下所示的注釋:

Bitmap.recycle引發的血案從Bitmap.recycle說起然而……

這裡居然寫着:The new bitmap may be the same object as source, or a copy may have been made.

看來還是真有可能為同一個對象的!

經過幾次嘗試,發現隻有在角度很小很小的時候,才會出現這個情況,兩個bitmap是同一個對象,是以,我隻能這樣猜測,當角度過小時,系統認為這是一張圖檔,沒有發生變化,那麼系統就直接引用同一個對象來進行操作,避免記憶體浪費。那麼這個角度是怎麼來的呢?繼續猜測,如圖所示:

Bitmap.recycle引發的血案從Bitmap.recycle說起然而……

當圖像的旋轉角度小餘兩個像素點之間的夾角時,圖像即使選擇也無法顯示,是以,系統完全可以認為圖像沒有發生變化,是以,注釋中的情況,是不是有可能就是說的這種情況呢?

我還沒有來得及繼續驗證,希望大家可以一起讨論下~有說的不對的還請指教。

然而,教訓是,在不相容Android2.3的情況下,别在使用recycle方法來管理Bitmap了,那是GC的事!

繼續閱讀