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);
    }
}
相關文章
相關標籤/搜索