TransitionDrawable使用不当导致内存泄露

最近要做类似网易云音乐背景高斯模糊的效果, 同时也想让背景变化时不要那么生硬, 就是下面这个效果

Google一番后决定用TransitionDrawable, 由于是配合UniversalImageLoader使用, 所以只需要实现一个BitmapDisplayer作为UIL的配置项就行了.



最初的代码是这样写的

private static class DrawableFadeDisplayer implements BitmapDisplayer {

    private final int durationMillis;

    public DrawableFadeDisplayer(int durationMillis) {
        this.durationMillis = durationMillis;
    }

    @Override
    public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
        ImageView imageview = (ImageView) imageAware.getWrappedView();
        Drawable oldDrawable = imageview.getDrawable();
        TransitionDrawable td = new TransitionDrawable(new Drawable[] {
                oldDrawable==null?(new ColorDrawable(Color.TRANSPARENT)):oldDrawable,
                new BitmapDrawable(Resources.getSystem(), bitmap)
            });
        imageview.setImageDrawable(td);
        td.startTransition(durationMillis);
    }
}

最关键的部分是display中的代码, 首先获取了旧的Drawable, 然后和新生成的BitmapDrawable一起构造一个TransitionDrawable, 最后调用startTransition就可以了.

简单明了. 实际使用中, 发现app占用的内存越来越高, 但只要退出Activity, 一两次GC之后内存就会降下来, 基本可以确定是这段代码造成了内存泄露.



问题出在这句代码

Drawable oldDrawable = imageview.getDrawable();

乍一看这句代码逻辑是没有问题的, 每次我们都是将旧的Drawable作为第一层, 新的Drawable作为第二层创建TransitionDrawable, 但是注意我们是创建的TransitionDrawable, 并将它设给ImageView, 也就是说我们调用getDrawable拿到的也是TransitionDrawable, 一个TransitionDrawable其实是持有多个Drawable的, 在这里是持有两个.

程序进行第一次渐变动画后, ImageView中的TransitionDrawable持有两个Drawable, 第二次渐变动画, 我们将TransitionDrawable和新的BitmapDrawable组合在一起创建一个新的TransitionDrawable.

简单示意一下ImageView持有的Drawable:

第一次渐变后:

TransitionDrawable(drawable0, drawable1)

第二次渐变后:

TransitionDrawable(
    TransitionDrawable(drawable0, drawable1),
    drawable2
)

第三次渐变后:

TransitionDrawable(
    TransitionDrawable(
        TransitionDrawable(drawable0, drawable1),
        drawable2
    ),
    drawable3
)

这样ImageView导致不能被回收的Drawable数量越来越多, 最终OOM.



所以我们正确的做法不应该是直接将getDrawable的值拿来当第一层Drawable, 而是先判断一下这个值的类型, 如果是TransitionDrawable, 应该获取它第二层Drawable作为我们的第一层, 这样原来的第一层Drawable就会失去到GC Roots的引用链, 从而可以被回收.

当然另一种思路是TransitionDrawable动画完成之后再将新的BitmapDrawable设给ImageView, 但并没有这个监听器, 最简单便捷的还是上面的思路.

最终代码修改成下面的样子, 主要是需要判断getDrawable的类型, 如果是TransitionDrawable, 就获取第二层Drawable.

private static class DrawableFadeDisplayer implements BitmapDisplayer {

    private final int durationMillis;

    public DrawableFadeDisplayer(int durationMillis) {
        this.durationMillis = durationMillis;
    }

    @Override
    public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
        ImageView imageview = (ImageView) imageAware.getWrappedView();
        Drawable oldDrawable = imageview.getDrawable();
        Drawable oldBitmapDrawable = null;
        if (oldDrawable == null) {
            oldBitmapDrawable = new ColorDrawable(Color.TRANSPARENT);
        } else if (oldDrawable instanceof TransitionDrawable) {
            oldBitmapDrawable = ((TransitionDrawable) oldDrawable).getDrawable(1);
        } else {
            oldBitmapDrawable = oldDrawable;
        }
        TransitionDrawable td = new TransitionDrawable(new Drawable[] {
                oldBitmapDrawable,
                new BitmapDrawable(Resources.getSystem(), bitmap)
            });
        imageview.setImageDrawable(td);
        td.startTransition(durationMillis);
    }
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-26 08:20:15

TransitionDrawable使用不当导致内存泄露的相关文章

fastjson反序列化使用不当导致内存泄露

分析一个线上内存告警的问题时,发现了造成内存告警的原因是使用fastjson不当导致的. 分析dump发现com.alibaba.fastjson.util.IdentityHashMap$Entry对象比较多. 查找相关文档: fastjson IdentityHashMap 内存泄漏排查 (这篇文档分析描述的情况与我们遇到的问题的原因一样,是使用com.alibaba.fastjson.util.ParameterizedTypeImpl不当导致的) fastjon官方在很早的版本就修复过类

Andorid 内存溢出与内存泄露,几种常见导致内存泄露的写法

内存泄露,大部分是因为程序的逻辑不严谨,但是又可以跑通顺,然后导致的,内存溢出不会报错,如果不看日志信息是并不知道有泄露的.但是如果一直泄露,然后最终导致的内存溢出,仍然会使程序挂掉.内存溢出大部分是关于图片的请求,然后又没有及时的释放内存,而导致的内存泄露. 下面是几种常见的导致内存泄露的写法.有些是收集的别的地方的,我也是看到才知道自己写错了,分享一下吧 1.单例造成的内存泄漏 大家都喜欢用Android的单例模式,不过使用的不恰当的话也会造成内存泄漏.因为单例的静态特性使得单例的生命周期和

hadoop1.0 TaskTracker因为分布式缓存导致内存泄露的一次问题排查

上周五同事到公司说凌晨的时候有值班同事打电话给他,有部分job卡住了,运行了很长时间都没运行完成,由于是凌晨,他没来得及详细的查看日志,简单的把有问题的tasktracker重启了一下,只有一个节点的TaskTracker进程停掉,让我查一下具体是什么问题.以下是排查过程: 1.登陆到停掉TT进程的处理机 (1).查看磁盘空间 磁盘没有出现空间不足的情况. (2).top查看负载和内存使用情况: 根据上图看出内存和负载都不算高,也不存在僵尸进程. 2.查看进程日志 1.log4j日志: 2014

BingMap频繁Add Pushpin和Delete Pushpin会导致内存泄露

最近在做性能测试的时候发现BingMap内存泄露(memory leak)的问题,查找了一些国外的帖子,发现也有类似的问题,但是没有好的解决办法. https://social.msdn.microsoft.com/Forums/en-US/3226f255-2ae1-4718-b848-5f24e76b64b0/your-pushpins-are-broken-addremove-leads-to-memory-leak?forum=bingmapsajax 经过一番尝试,找到了一个折中的解决

Handler导致内存泄露分析

Handler mHandler = new Handler() {    @Override    public void handleMessage(Message msg) {            // do something.    }}```当我们这样创建`Handler`的时候`Android Lint`会提示我们这样一个`warning: In Android, Handler classes should be static or leaks might occur.`. 一

Context使用不当造成内存泄露

一般来说,视频同步指的是视频和音频同步,也就是说播放的声音要和当前显示的画面保持一致.想象以下,看一部电影的时候只看到人物嘴动没有声音传出:或者画面是激烈的战斗场景,而声音不是枪炮声却是人物说话的声音,这是非常差的一种体验. 现在游戏市场分为,pc端,移动端,浏览器端,而已移动端和浏览器端最为接近.都是短平快的特殊模式,不断的开服,合服,换皮.如此滚雪球! 那么在游戏服务器架构的设计方面肯定是以简单,快捷,节约成本来设计的. http://www.cnblogs.com/nsrtuj/ 最近接到

memset函数导致内存泄露的问题

我们一般常说的内存泄漏是指堆内存的泄漏.程序从堆中分配的内存使用完毕后必须显式释放,否则这块内存就不能被再次使用,即这块内存泄漏了.内存泄漏导致软件在运行过程中占用越来越多的内存,程序的效率会越来越低,从而影响用户的体验,失去市场竞争力.  为了预防内存泄漏我们要求程序使用malloc.new等函数从堆中分配的内存必须在使用完后调用free.delete函数释放该内存.但是如果指向该内存指针的值被修改了,不再指向该内存了,那么即使调用free.delete函数也不会释放该内存,memset函数

引用计数gc机制使用不当导致内存泄漏

上一篇文章找同事review了一下,收到的反馈是铺垫太长了,我尽量直入正题,哈哈 最近dbd压测时发现内存泄漏,其实这个问题去年已经暴露了,参见这篇博客[压测周].当时排查不够仔细,在此检讨下.关于dbd的内存问题,还有这篇博客讨论线程安全,以及这篇博客讨论临时变量的处理.当时还存了一个尾巴,因为用到关联数组进行脏数据管理,这部分数据是怎么释放的一直没搞明白.今天算是搞明白了,这个内存泄漏的bug也源于此. 基于引用计数的gc机制,应该是在引用计数为0的时候释放内存的,lpc也基本没有例外.具体

静态变量导致的内存泄露

public  class MainActivity extends Activity{                private static final String TAG = "MainActivity";             private static Context sContext;                   @Override             protected void onCreate(Bundle savedInstanceState)