一个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内存的目的。