TransitionDrawable使用不当致使内存泄露

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

项目效果图

Google一番后决定用TransitionDrawable, 因为是配合UniversalImageLoader使用, 因此只须要实现一个BitmapDisplayer做为UIL的配置项就好了.ide

最初的代码是这样写的动画

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就能够了.this

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

问题出在这句代码内存

Drawable oldDrawable = imageview.getDrawable();

 

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

程序进行第一次渐变更画后, ImageView中的TransitionDrawable持有两个Drawable, 第二次渐变更画, 咱们将TransitionDrawable和新的BitmapDrawable组合在一块儿建立一个新的TransitionDrawable
简单示意一下ImageView持有的Drawable
第一次渐变后:it

TransitionDrawable(drawable0, drawable1)

 

第二次渐变后:io

TransitionDrawable(
    TransitionDrawable(drawable0, drawable1),
    drawable2
)

 

第三次渐变后:class

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);
    }
}
相关文章
相关标签/搜索