一个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. XFire构建web service客户端的五种方式

    这里并未涉及到JSR 181 Annotations 的相关应用,具体的三种方式如下 ① 通过WSDL地址来创建动态客户端 ② 通过服务端提供的接口来创建客户端 ③ 使用Ant通过WSDL文件来生成客 ...

  2. Spring MVC 3.0 深入及对注解的详细讲解[转载]

    http://blog.csdn.net/jzhf2012/article/details/8463783 核心原理 1.       用户发送请求给服务器.url:user.do 2.       ...

  3. 用exec调用带有output输出参数的存储过程

    用exec调用带有output输出参数的存储过程,想要得到输出参数的值,必须在调用参数后面加output关键字,如: declare @value int exec up_test    2,3,@v ...

  4. 利用百度翻译API,获取翻译结果

    利用百度翻译API,获取翻译结果 translate.py #!/usr/bin/python #-*- coding:utf-8 -*- import sys reload(sys) sys.set ...

  5. C#后台调用前台javascript的五种方法小结

    第一种,OnClientClick (vs2003不支持这个方法) <asp:Button ID="Button1" runat="server" Tex ...

  6. 自己写的jQuery放大镜插件效果(二)(采用只有一张图片的思路)

    废话不多说,先看效果图,和上一章节用的是同一个小图片: 这个方法实现的放大有个弊端就是放大倍数设置的过大的话,会带来图片上的模糊.但是图片加载的话要比使用2张图片加载的快很多 插件代码: ; (fun ...

  7. boost实现日期时间格式化

    #include <iostream> #include<thread> #include<chrono> #include<clocale> #inc ...

  8. 伯克利、OpenAI等提出基于模型的元策略优化强化学习

    基于模型的强化学习方法数据效率高,前景可观.本文提出了一种基于模型的元策略强化学习方法,实践证明,该方法比以前基于模型的方法更能够应对模型缺陷,还能取得与无模型方法相近的性能. 引言 强化学习领域近期 ...

  9. 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #18 向ext4转换

    HACK #18 向ext4转换 ext4可以与ext2/ext3在后台进行互换.这里将介绍从ext2/ext3转换的方法以及转换时的注意事项.转换有两种方法可以将ext2/ext3的磁盘映像作为ex ...

  10. 根据inode编号来删除文件或目录

    在Linux系统上,有时候会出现文件名为特殊字符的文件或目录,当我们使用rm来删除这样的文件或目录时,就会出错导致删不掉.但是我们可以依据inode号来删除这样的文件,方法如下: (1)执行ls -i ...