是否需要主动调用Bitmap的recycle方法?

来源:互联网 发布:软件添加广告sdk 编辑:程序博客网 时间:2024/06/06 03:52

一个图片加载到内存里,其实是有两部分数据组成,一部分是图片的相关描述信息,另一部分就是最重要的像素信息(这部分是有byte数组组成的),android系统为了提高对图片的处理效率,对于图片的处理都是调用了底层的功能(由C语言实现的),也就是说一个图片加载到内存里后是使用两部分的内存区域,简单的说:一部分是java可用的内存区,一部分是c可用的内存区,这两个内存区域是不能相互直接使用的,这个bitmap对象是由java分配的,当然不用的时候系统会自动回收了,可是那个对应的C可用的内存区域jvm是不能直接回收的,这个只能调用底层的功能释放。所以你要调用recycle方法来释放那一部分内存。


我们知道手机的内存有限,而图片所占的内存往往又很大。所以在处理图片的时候可以在服务端或者客户端提前将图片处理一下,减少其体积。另外使用Bitmap的时候可以使用SoftReference来及时释放资源。但是看到好多程序还是主动地调用Bitmap对象的recycle方法来释放资源。可能我们就有疑问了:不是java会自动回收内存吗,那干吗还要手动地去回收?
要解决这个问题,我们得去看看recycle的源码:
public void recycle() {
     if (!mRecycled) {
mBuffer = null;
nativeRecycle(mNativeBitmap);
mNinePatchChunk = null;
mRecycled = true;
}
}
通过看源码,我们会发现,这个方法首先将这个Bitmap的引用置为null,然后调用了nativeRecycle(mNativeBitMap)方法,这个方法很明显是个JNI调用,会调用底层的c或者c++代码就可以做到对该内存的立即回收,而不需要等待那不确定啥时候会执行的GC来回收了。
当然如果用的图片很少,占用的内存也很少的时候就没有必要手动调用recycle方法来回收内存了,GC会在合适的时候回收这些内存的。只有图片很多占用内存很多的时候才需要我们主动调用recycle方法,否则很有可能出现OOM异常的。

Google的官方文档中对recycle()这个方法的解释:

public void recycle ()

Added in API level 1
Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.


Android系统中Bitmap是否有调用recycle方法的必要性

http://blog.csdn.net/lwuit/article/details/7725333

http://developer.android.com/reference/android/graphics/Bitmap.Config.html
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
http://developer.android.com/training/displaying-bitmaps/manage-memory.html
--------------------------------------------------------------------------------------------------------------------------------------------------------------


感谢http://www.cnblogs.com/zhucai/p/5413340.html


一个Bitmap使用完后,是只需要等它成为垃圾后让GC去回收,还是应该主动调用recycle方法呢?或者说,主动调用recycle方法是否有好处,是否能马上回收内存呢?

带着这个问题来看源码(我看的4.4源码)。

先看Bitmap内存的创建,通过跟踪Bitmap.createBitmap方法,可以发现是native方法里调用的JVM来创建的:

jbyteArray arrayObj = env->NewByteArray(size);

native使用的是通过其得到的一个固定地址:

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

native里会用一个SkPixelRef来存放他们:

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

然后将这个传给Bitmap:

bitmap->setPixelRef(pr);

再看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内存的目的。

阅读全文
0 0
原创粉丝点击