zoukankan      html  css  js  c++  java
  • 是否需要主动调用Bitmap的recycle方法

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

  • 相关阅读:
    PyCharm配置SFTP远程调试Django应用
    linux安装mysql详细步骤
    一些unity问题的收集
    主程之路
    【英宝通Unity4.0公开课学习 】(六)76讲到90讲
    【英宝通Unity4.0公开课学习 】(五)47讲到75讲
    【英宝通Unity4.0公开课学习 】(四)GUI到物理引擎
    【英宝通Unity4.0公开课学习 】(三)脚本使用
    【英宝通Unity4.0公开课学习 】(二)场景创建
    【英宝通Unity4.0公开课学习 】(一)资源管理
  • 原文地址:https://www.cnblogs.com/zhucai/p/5413340.html
Copyright © 2011-2022 走看看