是否須要主動調用Bitmap的recycle方法

一個Bitmap使用完後,是隻須要等它成爲垃圾後讓GC去回收,仍是應該主動調用recycle方法呢?或者說,主動調用recycle方法是否有好處,是否能立刻回收內存呢?java

 

帶着這個問題來看源碼(我看的4.4源碼)。app

 

先看Bitmap內存的建立,經過跟蹤Bitmap.createBitmap方法,能夠發現是native方法裏調用的JVM來建立的:spa

    jbyteArray arrayObj = env->NewByteArray(size);指針

native使用的是經過其獲得的一個固定地址:對象

    jbyte* addr = jniGetNonMovableArrayElements(&env->functions, arrayObj);ip

native裏會用一個SkPixelRef來存放他們:內存

    SkPixelRef* pr = new AndroidPixelRef(env, bitmapInfo, (void*) addr, bitmap->rowBytes(), arrayObj, ctable);資源

而後將這個傳給Bitmap:rem

    bitmap->setPixelRef(pr);get

 

再看recycle過程:

java端:

    mBuffer = null;

native端:

    Caches::getInstance().textureCache.removeDeferred(bitmap);
    fPixelRef->unref();   // fPixelRef是上面分配的SkPixelRef
    fPixelRef = NULL;

這裏其實就是java端將mBuffer置爲垃圾。native端釋放SkPixelRef,並延遲刪除其對應的TextureCache(最終的刪除應該是在下一幀開始前)。

 

再看Bitmap的finalize,發現Bitmap類本身沒有finalize,專門用了一個靜態內部類BitmapFinalizer,其finalize方法來作native資源的釋放。至於爲何要這麼弄,我後面另說。

其會調用到native裏:

    Caches::getInstance().textureCache.removeDeferred(resource);
    delete resource;

延遲刪除其對應的TextureCache,並刪除SkBitmap。

 

從這麼來看,recycle方法會釋放部分native內存,但並不會釋放Bitmap佔用內存最大的圖像數據內存。
但我忽然想到,好像截屏時獲得的Bitmap的圖像數據內存並非在JVM裏申請的,查看代碼,果真是這樣。其並非經過Bitmap.createBitmap方法建立的圖像:

    GraphicsJNI::createBitmap(env, bitmap, GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);

其使用的SkPixelRef也不是上面的AndroidPixelRef,而是ScreenshotPixelRef,裏面持有着圖像數據。

在這種狀況下,調用recycle方法是會釋放其圖像數據的。

另外要命的是,假如咱們不停截屏並丟掉以前的Bitmap,咱們可能以爲很容易就會有垃圾回收,那麼以前的Bitmap就回收了。但是因爲Bitmap的圖像數據纔是內存大戶,Bitmap自己佔用內存很是小,所以這種狀況下Bitmap的構造引發垃圾回收的可能性很低。

我作了個試驗,在app裏點擊按鈕截一次屏,而後立刻扔掉,使其爲垃圾。我不停點擊按鈕,最終系統內存耗盡致使app被殺,也沒有發生GC。改成每次截屏後手動調用gc,就不會致使內存增大。

而且危險的是,這部份內存並非分配在app端,就算app被殺也不會釋放。(截屏的內存是在SurfaceFlinger端申請的,app端的釋放應該只是把內存使用權還給SurfaceFlinger,SurfaceFlinger會繼續重用它,但不會完全釋放還給系統,所以變大以後不會變小,不清楚有沒有最終的釋放邏輯。我之前在作系統截屏的時候曾由於沒有主動recycle致使佔用極大系統內存。)

 

畫個表格來講明一下recycle在各類狀況下會回收哪些內存吧:

 

SkPixelRef

(小)

SkBitmap

(小)

圖像數據

(面積大則很大)

TextureCache

(同圖像數據至關)

JVM中分配圖像數據如Bitmap.createBitmap

且沒有被硬件加速draw過

× × 無此內存

JVM中分配圖像數據如Bitmap.createBitmap

且有被硬件加速draw過

× ×

不在JVM中分配圖像數據如截屏

× 狀況同上

這些內存在其Bitmap成爲垃圾後的垃圾回收過程裏都會釋放。可是,在native內存吃緊的狀況下系統是不知道能夠經過GC來回收一部分native內存的,因此儘早釋放是有積極做用的。

 

結論:儘快的調用recycle是個好習慣,會釋放與其相關的native分配的內存;但通常狀況下其圖像數據是在JVM裏分配的,調用recycle並不會釋放這部份內存。

咱們用createBitmap建立的Bitmap且沒有被硬件加速Canvas draw過,則主動調用recycle產生的意義比較小,僅釋放了native裏的SkPixelRef的內存,這種狀況我以爲能夠不主動調用recycle。

被硬件加速Canvas draw過的因爲有TextureCache應該儘快調用recycle來儘早釋放其TextureCache。

像截屏這種不是在JVM裏分配內存的狀況也應該儘快調用recycle來立刻釋放其圖像數據。

(一個例外,若是是經過Resources.getDrawable獲得的Bitmap,不該該調用recycle,由於它可能會被重用)

 

另,說一下上面提到的Bitmap的finalize實現方式。
這裏沒有直接在Bitmap上實現finalize,而是用一個靜態內部類專門實現finalize,這是由於在GC過程當中,沒有finalize的對象能夠直接回收,而有finalize的對象須要多保持一下子來執行其finalize方法,而後才能回收,在我之前瞭解的C#的垃圾回收機制裏,有finalize的對象在第一次GC的時候只會執行其finalize方法,要到下一次GC纔會回收其內存。因此Bitmap的這種finalize實現方式是爲了讓佔用內存大的部分(Bitmap類)沒有finalize,能夠早點釋放;BitmapFinalizer內部類僅持有一個NativeBitmap指針,經過finalize去釋放native內存。這樣最有效的達到既提早釋放主要內存又能經過finalize釋放native內存的目的。

相關文章
相關標籤/搜索