Android handle內存泄漏

一,什麼是內存泄漏

首先我們要有堆內存的概念,堆內存是用來存放對象的,堆內存和棧內存的概念可以參考這篇文章https://www.cnblogs.com/liyonghua/p/8805017.html

jvm只有一個堆區(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身,幾乎所有的對象實例和數組都在堆中分配。當對象不被引用時,會被gc回收,釋放佔用的堆內存。如果不再用到的對象,被錯誤引用,而無法被回收,這就發生了“內存泄漏”

二,handle內存泄漏的原因

非內部類默認會持有外部類的引用,這樣當Activity被銷燬時,由於被匿名handler對象所持有而不能被釋放,Activity所佔用的內存就會泄露。

public class HandleTestActivity extends AppCompatActivity {

    private TextView tv;
    private int id = 0;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e("HandleTestActivity", "handleMessage");
            tv.setText(msg.arg1 + "");
            Toast.makeText(HandleTestActivity.this, msg.arg1 + " handleMessage", Toast.LENGTH_LONG).show();
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e("HandleTestActivity", "onCreate");
        setContentView(R.layout.activity_handle_test);
        tv = findViewById(R.id.tv);
        Message msg = handler.obtainMessage();
        ++id;
        msg.arg1 = id;
        handler.sendMessageDelayed(msg, 2000);
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e("HandleTestActivity", "onPause");
        Message msg = handler.obtainMessage();
        ++id;
        msg.arg1 = id;
        handler.sendMessageDelayed(msg, 100000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e("HandleTestActivity", "onDestroy");
        //我們可以在Activity onDestroy()時調用handler.removeCallbacksAndMessages(null),
        // 這樣就把queue裏所有的message都remove掉了,之前說過message被message pool回收掉會reset,
        // 因此不會再引用handler,這條引用鏈就斷掉了。
//        handler.removeCallbacksAndMessages(null);
    }

    /**
     * 當垃圾回收確定不再有對對象的引用時,由垃圾回收器對對象調用。
     * 子類重寫@code finalize方法以釋放系統資源或執行其他清理。
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        Log.e("HandleTestActivity", "finalize");
    }
}

對於非靜態的內部類,內部類的創建依賴外部類的實例對象,在沒有外部類實例之前是無法創建內部類的。所以非內部類默認會持有外部類的引用,可以查看build後的class文件,文件路徑 app\build\intermediates\javac\

文件展開如下,HandleTestActivity$1 表示HandleTestActivity爲外部類,1代表匿名內部類,構造方法傳了HandleTestActivity的實例。非靜態的內部類也是類似的!

 

 

class HandleTestActivity$1 extends Handler {
    HandleTestActivity$1(HandleTestActivity this$0) {
        this.this$0 = this$0;
    }

    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.e("HandleTestActivity", "handleMessage");
        HandleTestActivity.access$000(this.this$0).setText(msg.arg1 + "");
        Toast.makeText(this.this$0, msg.arg1 + " handleMessage", 1).show();
    }
}

當啓動HandleTestActivity後再finish,可以通過Android studio的profiler工具查看引用是否被釋放

handler實例引用了Activity,handler又被其messsage.target所引用,message放入message queue中,message queue生命週期結束前,message queue都間接引用了activity,導致內存泄漏!

也有些文章提到,可以在Activity onDestroy()時調用handler.removeCallbacksAndMessages(null),這樣確實message queue對activity的引用鏈沒有了,如下圖,但是handle對activity的引用鏈仍然存在

可以看到handle仍然引用着當前的this

三。解決方法

最好的解決方式就是使用靜態內部類解決

public class HandleTest2Activity extends AppCompatActivity {

    private TextView tv;
    private int id = 0;
    private Handler handler = new Myhandle(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e("HandleTest1Activity", "onCreate");
        setContentView(R.layout.activity_handle_test2);
        tv = findViewById(R.id.tv);
        Message msg = handler.obtainMessage();
        ++id;
        msg.arg1 = id;
        handler.sendMessageDelayed(msg, 2000);
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e("HandleTest1Activity", "onPause");
        Message msg = handler.obtainMessage();
        ++id;
        msg.arg1 = id;
        handler.sendMessageDelayed(msg, 40000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e("HandleTest1Activity", "onDestroy");
//        handler.removeCallbacksAndMessages(null);
    }

    /**
     * 當垃圾回收確定不再有對對象的引用時,由垃圾回收器對對象調用。
     * 子類重寫@code finalize方法以釋放系統資源或執行其他清理。
     *
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        Log.e("HandleTest1Activity", "finalize");
    }

    private static class Myhandle extends Handler {
        private WeakReference<Context> reference;//弱引用
        public Myhandle(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Context context = reference.get();
            Log.e("Myhandle", "handleMessage context=" + context);
            if (null != context){
                Toast.makeText(context, msg.arg1 + " handleMessage", Toast.LENGTH_LONG).show();
            }
        }
    }
}

靜態內部類編譯好的class文件,對照HandleTestActivity$1,構造方法沒有傳入外部類的對象

class HandleTest2Activity$Myhandle extends Handler {
    private WeakReference<Context> reference;

    public HandleTest2Activity$Myhandle(Context context) {
        this.reference = new WeakReference(context);
    }

    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Context context = (Context)this.reference.get();
        Log.e("Myhandle", "handleMessage context=" + context);
        if (null != context) {
            Toast.makeText(context, msg.arg1 + " handleMessage", 1).show();
        }

    }
}

此時看運行完的結果,此時已經沒有handle對activity的引用了

四,leakcanary

內存泄漏也可以使用leakcanary分析

 

源碼地址 https://dev.tencent.com/u/kuangxuefeng/p/AndroidTestDemo/git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章