Bitmap.recycle引发的血案

从Bitmap.recycle说起

在Android中,Bitmap的存储分为两部分,一部分是Bitmap的数据,一部分是Bitmap的引用。

在Android2.3时代,Bitmap的引用是放在堆中的,而Bitmap的数据部分是放在栈中的,需要用户调用recycle方法手动进行内存回收,而在Android2.3之后,整个Bitmap,包括数据和引用,都放在了堆中,这样,整个Bitmap的回收就全部交给GC了,这个recycle方法就再也不需要使用了。

然而……

现在的SDK中对recycle方法是这样注释的,如图所示:

可以发现,系统建议你不要手动去调用,而是让GC来进行处理不再使用的Bitmap。我们可以认为,即使在Android2.3之后的版本中去调用recycle,系统也是会强制回收内存的,只是系统不建议这样做而已。

鄙司代码有些是从Android2.3出来的,因此很多地方还在使用Bitmap.recycle。通常情况下,这也没什么问题,但是,今天遇到一个bug引发了Bitmap.recycle的血案。

起因

这个bug的起因是因为我们的一张图片需要旋转,同时可以设置一个旋转角度,老的代码是这样写的:

ImageView imageView = (ImageView) findViewById(R.id.test);
Matrix matrix = new Matrix();
matrix.setRotate(0.013558723994643297f);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
Bitmap targetBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
if (!bitmap.isRecycled()) {
    bitmap.recycle();
}
imageView.setImageBitmap(targetBmp);

除了中间的0.013558723994643297f这串比较奇葩的数据(当然,正常情况下都是20、30这样正常的数),其它都是比较正常的代码。

但实际上,只要一运行这段代码,程序就会崩溃,错误原因如下所示:

E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: com.xys.preferencetest, PID: 30512
                  java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@1a50ff6b

这个问题一看就知道是由于Bitmap被调用recycle方法回收后,又调用了Bitmap的一些方法而导致的。可是,代码中可以发现我们recycle的是bitmap而不是通过Bitmap.createBitmap重新生成的targetBmp,为什么会报这个exception呢?

注释

按道理来说,bitmap与create出来的targetBmp应该是两个对象,当旋转角度正常的时候,确实也是这样,但当旋转角度比较奇葩的时候,这两个bitmap对象居然变成了同一个!而打开Bitmap.createBitmap的代码,可以发现如下所示的注释:

这里居然写着:The new bitmap may be the same object as source, or a copy may have been made.

看来还是真有可能为同一个对象的!

猜测

经过几次尝试,发现只有在角度很小很小的时候,才会出现这个情况,两个bitmap是同一个对象,因此,我只能这样猜测,当角度过小时,系统认为这是一张图片,没有发生变化,那么系统就直接引用同一个对象来进行操作,避免内存浪费。那么这个角度是怎么来的呢?继续猜测,如图所示:

当图像的旋转角度小余两个像素点之间的夹角时,图像即使选择也无法显示,因此,系统完全可以认为图像没有发生变化,因此,注释中的情况,是不是有可能就是说的这种情况呢?

我还没有来得及继续验证,希望大家可以一起讨论下~有说的不对的还请指教。

然而……

然而,教训是,在不兼容Android2.3的情况下,别在使用recycle方法来管理Bitmap了,那是GC的事!

时间: 2025-01-02 04:21:18

Bitmap.recycle引发的血案的相关文章

Replication的犄角旮旯(六)-- 一个DDL引发的血案(上)(如何近似估算DDL操作进度)

原文:Replication的犄角旮旯(六)-- 一个DDL引发的血案(上)(如何近似估算DDL操作进度) <Replication的犄角旮旯>系列导读 Replication的犄角旮旯(一)--变更订阅端表名的应用场景 Replication的犄角旮旯(二)--寻找订阅端丢失的记录 Replication的犄角旮旯(三)--聊聊@bitmap Replication的犄角旮旯(四)--关于事务复制的监控 Replication的犄角旮旯(五)--关于复制identity列 Replicati

Replication的犄角旮旯(七)-- 一个DDL引发的血案(下)(聊聊logreader的延迟)

原文:Replication的犄角旮旯(七)-- 一个DDL引发的血案(下)(聊聊logreader的延迟) <Replication的犄角旮旯>系列导读 Replication的犄角旮旯(一)--变更订阅端表名的应用场景 Replication的犄角旮旯(二)--寻找订阅端丢失的记录 Replication的犄角旮旯(三)--聊聊@bitmap Replication的犄角旮旯(四)--关于事务复制的监控 Replication的犄角旮旯(五)--关于复制identity列 Replicat

一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁

前续 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(一)——地:起因 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁 平行时空 在复制好上面那一行我就先停下来了,算是先占了个位置,虽然我知道大概要怎么写,不过感觉还是很乱. 我突然想到,既然那么纠结,那么混乱,那么不知所措,我们不如换个视角.记得高中时看过的为数不多的长篇小说<穆斯林的葬礼>,作者是:霍达(女),故事描写了两个发生在不同时代.有着不同的内容却又交错扭结的爱情悲剧,一个是“玉”的故事,一个是“月”

一个无锁消息队列引发的血案(六)——RingQueue(中) 休眠的艺术 [续]

目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的艺术 [续] 开篇 这是第五篇的后续,这部分的内容同时会更新和添加在 第五篇:RingQueue(中) 休眠的艺术 一文的末尾. 归纳 紧接上一篇的末尾,我们把 Windows 和 Linux 下的休眠策略归纳总结一下,如下图: 我们可以看到,Linux 下的 sched_yield() 虽然包括了

openstack运维实战系列(十三)之glance更改路径引发的&quot;血案&quot;

1. 背景说明 glance在openstack中负责镜像相关的服务,支持将运行的虚拟机转换为快照,镜像和快照都存储在glance中,glance的后端支持多种存储方式,包括本地的文件系统,http,glusterfs,ceph,swift等等. 默认情况下,glance采用本地文件系统的方式存储image,存储的路径为/var/lib/glance/images,随着时间的推移,当镜像越来越多的时候,根目录的空间将会越来越大,所以对于glance的路径来说,需要提前做好规划和准备,如划分一个单

一个二级菜单引发的血案

近期发现自己css不是很好,于是又看了一遍<css权威指南>.总感觉自己抓不到重点.弃疗中...于是看看其他书.然后学妹跟我说她的二级菜单写得很乱.当时我心里就在想二级菜单,有何难?自认为10分钟能搞定.跟她要效果图并很自大的说了句“等会儿,我写个简单的”.于是血案由此引发... 二级菜单要实现的原效果图是: (如发现雷同,不是巧合,是我从别的网页上截屏下来的 ~_~).既然说了简单,肯定效果没这么精美.但是至少基本效果和原理要实现. 10分钟过去了....15分钟过去了....这个“等会儿”

模板链接与前置声明引发的血案

模板链接与前置声明引发的血案 模板链接与前置声明引发的血案 现象 问题原型 模板參数类型类 使用类模板的类 分析 objdump -S TemplateLink SUPERSUBCLASS 分析 objdump -S UsingBaseo objdump -S UsingChildo 问题解答 解答问题一 解答问题二 解决方式 类型萃取辅助类 应用 不足 现象: 有一个类模板,它会依据模板类型參数T的实际类型,调用不同的实例化泛型函数子去处理实际事情. 在程序运行时.发如今不同的模块中用相同的类

一个Sqrt函数引发的血案

我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这个经常调用的函数呢? 虽然有可能你平时没有想过这个问题,不过正所谓是"临阵磨枪,不快也光",你"眉头一皱,计上心来",这个不是太简单了嘛,用二分的方法,在一个区间中,每次拿中间数的平方来试验,如果大了,就再试左区间的中间数:如果小了,就再拿右区间的中间数来试.比如求sqrt(16)的结果,你先试

一次优化引发的血案

前些天一个Nginx+PHP项目上线后遭遇了性能问题,于是打算练练手,因为代码并不是我亲自写的,所以决定从系统层面入手看看能否做一些粗线条的优化. 首先,我发现服务的Backlog设置过小,可以通过ss命令查询Send-Q来确认: shell> ss -ln Recv-Q Send-Q Local Address:Port Peer Address:Port 0 511 *:80 *:* 0 128 127.0.0.1:9000 *:* 明显看出,Nginx的Backlog是511:PHP的Ba