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

是否需要主动调用Bitmap的recycle方法的更多相关文章

  1. mui---子页面主动调用父页面的方法

    我们在做APP的时候,很多时候会有这样的功能需求,例如:登录,充值,如果登录成功,或充值成功后,需要更改当前页面以及父页面的状态信息,就会用到在子页面调用父页面的方法来实现:在子页面刷新父页面的功能. ...

  2. vue--父组件主动获取子组件的方法

    父组件主动获取子组件的方法和属性 第一步:调用自组件的时候,给自组建定义一个Header <v-header ref='headerInfo'></v-header> 第二步: ...

  3. 一、Bitmap的recycle问题

    尽管Android有自己的垃圾回收机制,对于是不是要我们自己调用recycle,还的看情况而定.假设仅仅是使用少量的几张图片,回收与否关系不大.但是若有大量bitmap须要垃圾回收处理,那必定垃圾回收 ...

  4. Bitmap的recycle问题

        虽然Android有自己的垃圾回收机制,对于是不是要我们自己调用recycle,还的看情况而定.如果只是使用少量的几张图片,回收与否关系不大.可是若有大量bitmap需要垃圾回收处理,那必然垃 ...

  5. Android处理Bitmap的一些方法

    http://www.it165.net/pro/html/201305/5795.html # 文件与Bitmap间的方法 1. 从文件载入Bitmap 01./** 02.* @brief 从文件 ...

  6. react中直接调用子组件的方法(非props方式)

    我们都知道在 react中,若要在父组件调用子组件的方法,通常我们会采用在父组件定义一个方法,作为props转给子组件,然后执行该方法,可以获取到子组件传回的参数以得到我们的目的. 显而易见,这个执行 ...

  7. 【vue】父组件主动调用子组件 /// 非父子组件传值

    一  父组件主动调用子组件: 注意:在父组件使用子组件的标签上注入ref属性,例如: <div id="home"> <v-header ref="he ...

  8. Android 调用系统相机拍照保存以及调用系统相册的方法

    系统已经有的东西,如果我们没有新的需求的话,直接调用是最直接的.下面讲讲调用系统相机拍照并保存图片和如何调用系统相册的方法. 首先看看调用系统相机的核心方法: Intent camera = new ...

  9. oracle调用JAVA类的方法

    导入jar包 在oracle中导入需要的jar包,我们把编辑好的java类打成jar包,直接在oarcle里面写简单的调用就可以了,  1.操作系统需要拥有支持loadjava命令的jdk.  2.加 ...

随机推荐

  1. java IO切割流合并流

    切割流,将一个较大的文件,切割成多个小文件存储

  2. 手动安装Android Support Library(23.0.1)

    在搭建React-Native开发环境的时候,使用Android Sdk Manager无法找到Android Support Library这一项. 所以google了一下,找到了解决办法. 访问A ...

  3. 多线程实现ping扫描

    代码: # coding=utf-8 import subprocess from Queue import Queue import threading class Pinger(object): ...

  4. request响应码记录

    响应 -- r.status_code                     #响应状态码 -- r.content                             #会自动为你解码 字节方 ...

  5. post 中文数据到elasticsearch restful接口报json_parse_exception 问题

    我们的客户端程序直接调用es 的restful接口, 通过post json数据去查询, 但post数据有中文的时候,有些中文会报异常,有些中文不会 {"error":{" ...

  6. ubuntu solute two different terminals cmd

    sudo update-alternatives --config x-terminal-emulator select 1. gnome-xterminal

  7. echo off

    就是说关闭回显 @echo off并不是DOS程序中的,而是DOS批处理中的.当年的DOS,所有操作都用键盘命令来完成,当你每次都要输入相同的命令时,可以把这么多命令存为一个批处理,从此以后,只要运行 ...

  8. Oracle创建数据库链接

    **********创建数据库链接******************create public database link link_gzzl connect to system identifie ...

  9. 如何分析 WindowsDump:Dump 起源与初始设置

    https://www.qcloud.com/community/article/511817 转者注:让我感觉以前看蓝屏都白看了~~~原来蓝屏也可以分析具体原因. 适用场景:Windows 系列系统 ...

  10. python之ConfigParser

    以前傻傻的不知道还有configParser这么方便的模块,都是一个个的解析转换…… 配置文件xxxxx # 注释1 ;  注释2 [section1] # 节点 k1 = v1    # 值 k2: ...