码迷,mamicode.com
首页 > 其他好文 > 详细

TransitionDrawable使用不当导致内存泄露

时间:2015-07-12 01:50:15      阅读:158      评论:0      收藏:0      [点我收藏+]

标签:内存泄漏   android   drawable   

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

技术分享

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);
    }
}

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

TransitionDrawable使用不当导致内存泄露

标签:内存泄漏   android   drawable   

原文地址:http://blog.csdn.net/shaw1994/article/details/46846075

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!