一個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內存的目的。