Android中的內部類引起的內存泄露


引子

什么是內部類?什么是內存泄露?為什么Android的內部類容易引起內存泄露?如何解決?


什么是內部類?

什么是內部類?什么又是外部類、匿名類、局部類、頂層類、嵌套類?大家可以參考我這篇文章 ,再查查一些資料,先弄清楚什么是內部類和內部類的特性再向下看。

經常會遇見Android程序中這樣使用handler:

public class SomeActivity {

    // ......
    
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what) {
            case 0:
                // do something
                break;
            case 1:
                // do something
                break;
            default:
                break;
            }
        }
    };
    
    private void someMethod () {
        mHandler.sendEmptyMessage(0);
    }
}

上述代碼中,mHandler字段指向一個匿名Handler類。匿名類是內部類嗎?匿名類會持有外部類的對象嗎? 答案是:匿名類是內部類,但是是特殊的內部類,如果把匿名類放到一個static方法中,它是不會持有外部類實例的。而在上面的代碼中,這個mHandler會持有外部類(SomeActivity)實例的引用,因為它處於一個對象的上下文中,而不是類型上下文中。

什么是”持有外部類實例的引用“?你可以這么理解:

public class InnerClass {
    private OuterClass outer;
    public InnerClass(OuterClass outer) {
        this.outer = outer;
    }
}

就是說,創建InnerClass對象的時,必須傳遞進去一個OuterClass對象,賦值給InnerClass的一個字段outer,該字段是OuterClass對象的引用。回憶一下GC原理,如果InnerClass對象沒有被標記為垃圾對象,那么outer指向的OuterClass對象會可能被標記為垃圾對象嗎?答案是:InnerClass對象與GC Root有引用路徑,InnerClass對象又引用了OuterClass對象,那么OuterClass對象到GC Root也是有引用路徑的,所以,OuterClass不可能是垃圾對象。


為什么發生內存泄露?

由上文可以看出:當mHandler沒有被回收時,其外圍Activity對象不能被回收。當Activity被用戶關閉(finish),而此時mHandler還未被回收,那么Activity對象就不會被回收,造成Activity內存泄露。

問題的關鍵轉入到了這個問題:為什么Activity被finish了,mHandler還不能被回收?

發送消息時,我們使用了這個函數:mHandler.sendEmptyMessage(0)函數。通過查看源碼追蹤調用關系,發現走到了:

Message對象有個target字段,該字段是Handler類型,引用了當前Handler對象。一句話就是:你通過Handler發往消息隊列的Message對象持有了Handler對象的引用。假如Message對象一直在消息隊列中未被處理釋放掉,你的Handler對象就不會被釋放,進而你的Activity也不會被釋放。

這種現象很常見,當消息隊列中含有大量的Message等待處理,你發的Message需要等幾秒才能被處理,而此時你關閉Activity,就會引起內存泄露。如果你經常send一些delay的消息,即使消息隊列不繁忙,在delay到達之前關閉Activity也會造成內存泄露。


有什么解決方案?

方案#1:在關閉Activity時(finish/onStop等函數中),取消還在排隊的Message:
mHandler.removeCallbacksAndMessages(null);

方案#2:使用WeakReference截斷StrongReference。問題的症結既然是內部類持有外部類對象的引用,那我不用內部類就行了,直接使用靜態成員類。但mHandler又需要與Activity對象交互,那就來個WeakReference,指向外部Activity對象。

public class SomeActivity {
    private Handler mHandler = new MyHandler(this);
    private static class MyHandler extends Handler {
        private WeakReference<SomeActivity> ref;
        public MyHandler(SomeActivity activity) {
            if (activity != null) {
                ref = new WeakReference<SomeActivity>(activity);
            }
        }
        @Override
        public void handleMessage(Message msg) {
            if (ref == null) {
                return;
            }
            SomeActivity v = ref.get();
            if (v == null) {
                return;
            }
            // handle message
        }
    }
}

當Activity想關閉銷毀時,mHandler對它的弱引用沒有影響,該銷毀銷毀;當mHandler通過WeakReference拿不到Activity對象時,說明Activity已經銷毀了,就不用處理了,相當於丟棄了消息。

另外,當你使用Handler有內存泄露時候,Android Studio的Lint會有如下提示:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM