您的位置:首页 > 移动开发 > Android开发

TransitionDrawable使用不当导致内存泄露

2015-07-12 00:42 681 查看
最近要做类似网易云音乐背景高斯模糊的效果, 同时也想让背景变化时不要那么生硬, 就是下面这个效果



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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息