是否需要主动调用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内存的目的。
是否需要主动调用Bitmap的recycle方法的更多相关文章
- mui---子页面主动调用父页面的方法
我们在做APP的时候,很多时候会有这样的功能需求,例如:登录,充值,如果登录成功,或充值成功后,需要更改当前页面以及父页面的状态信息,就会用到在子页面调用父页面的方法来实现:在子页面刷新父页面的功能. ...
- vue--父组件主动获取子组件的方法
父组件主动获取子组件的方法和属性 第一步:调用自组件的时候,给自组建定义一个Header <v-header ref='headerInfo'></v-header> 第二步: ...
- 一、Bitmap的recycle问题
尽管Android有自己的垃圾回收机制,对于是不是要我们自己调用recycle,还的看情况而定.假设仅仅是使用少量的几张图片,回收与否关系不大.但是若有大量bitmap须要垃圾回收处理,那必定垃圾回收 ...
- Bitmap的recycle问题
虽然Android有自己的垃圾回收机制,对于是不是要我们自己调用recycle,还的看情况而定.如果只是使用少量的几张图片,回收与否关系不大.可是若有大量bitmap需要垃圾回收处理,那必然垃 ...
- Android处理Bitmap的一些方法
http://www.it165.net/pro/html/201305/5795.html # 文件与Bitmap间的方法 1. 从文件载入Bitmap 01./** 02.* @brief 从文件 ...
- react中直接调用子组件的方法(非props方式)
我们都知道在 react中,若要在父组件调用子组件的方法,通常我们会采用在父组件定义一个方法,作为props转给子组件,然后执行该方法,可以获取到子组件传回的参数以得到我们的目的. 显而易见,这个执行 ...
- 【vue】父组件主动调用子组件 /// 非父子组件传值
一 父组件主动调用子组件: 注意:在父组件使用子组件的标签上注入ref属性,例如: <div id="home"> <v-header ref="he ...
- Android 调用系统相机拍照保存以及调用系统相册的方法
系统已经有的东西,如果我们没有新的需求的话,直接调用是最直接的.下面讲讲调用系统相机拍照并保存图片和如何调用系统相册的方法. 首先看看调用系统相机的核心方法: Intent camera = new ...
- oracle调用JAVA类的方法
导入jar包 在oracle中导入需要的jar包,我们把编辑好的java类打成jar包,直接在oarcle里面写简单的调用就可以了, 1.操作系统需要拥有支持loadjava命令的jdk. 2.加 ...
随机推荐
- C语言扩展动态内存报错:realloc(): invalid next size: 0x0000000002365010 ***
晚上被这个内存扩展崩溃的问题折腾的有点崩溃,当答案揭晓的那一刻,恍然大悟,原来如此简单. 练习题目:输入一个字符串,根据字母进行排序,说白了就是一个简单的冒泡 #include <stdio.h ...
- Openwrt编译时修改默认IP的方法
在~/openwrt/barrier_breaker/package/base-files/files/lib/functions/ uci-defaults.sh 第178行修改IP地址
- debian下配置keepalived ha
抄袭自http://blog.51yip.com/server/1417.html,做了一些修改 可以参考http://blog.linuxphp.org/archives/1615/ 备注:NAT模 ...
- Yii 用户登录验证
http://blog.sina.com.cn/s/blog_685213e70101mo4i.html 1)首先在model文件夹中新建文件 LoginForm.php 代码如下 <?php ...
- 如何制作Jar包并在android中调用jar包
android制作jar包: 新建android工程,然后右击,点击导出,选择导出类型为Java下的JAR file,在java file specification 中不要选择androidmani ...
- Celery 异步定时周期任务
1/什么是Celery Celery 是基于Python实现的模块,用于执行异步定时周期任务的 其结构的组成是由 1.用户任务app 2.管道 broker 用于存储任务 官方推荐 redis rab ...
- jenkins 使用的python 不是指定的python 的解决方法
构建的时候加上要使用python的解析器路径 终端 which python 可以找到 python编辑器里面 import os os.system("which python" ...
- Java-Web中访问某个指定工程中的文件,报错后发现访问的文件是另一个工程里面的文件
问题: 浏览器向我的bingou项目中的UserDaoImpl.java发送请求, myeclipse报错:空指针异常 点击报错行之后,错误给定位到了另一个项目中的的一个文件 解决: 原因是文件名错误 ...
- ASP.NET Web Pages:简介
ylbtech-.Net-ASP.NET Web Pages:简介 ASP.NET 是一个使用 HTML.CSS.JavaScript 和服务器脚本创建网页和网站的开发框架. ASP.NET 支持三种 ...
- mysql binlog协议分析--具体event
这几天在修改canal, 连接mysql和maria接收到的event有所区别 拿一个简单的insert sql来举例 mysql 会有以下几个event写入到binlog里 1.ANONYMOUS_ ...