1. 程式人生 > >Android實戰技巧之三十八:Handler使用中可能引發的內存泄漏

Android實戰技巧之三十八:Handler使用中可能引發的內存泄漏

sha 指向 ons har 引用 destroy 對象 from weak

問題描寫敘述

曾幾何時,我們用原來的辦法使用Handler時會有以下一段溫馨的提示:

This Handler class should be static or leaks might occur

以下是更具體的說明(Android Studio上的警告,不知道Eclipse上是否同樣)

Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

大概意思就是:

一旦Handler被聲明為內部類,那麽可能導致它的外部類不可以被垃圾回收。假設Handler是在其它線程(我們通常成為worker thread)使用Looper或MessageQueue(消息隊列)。而不是main線程(UI線程),那麽就沒有這個問題。

假設Handler使用Looper或MessageQueue在主線程(main thread),你須要對Handler的聲明做例如以下改動:
聲明Handler為static類。在外部類中實例化一個外部類的WeakReference(弱引用)而且在Handler初始化時傳入這個對象給你的Handler;將全部引用的外部類成員使用WeakReference對象。

解決方式一

上面的描寫敘述中基本上把推薦的改動方法明白表達了出來。以下的代碼是我自己使用中的一個實現,請參考:

private CopyFileHandler mHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_appstart);
    mHandler = new CopyFileHandler(this);
    startCopyDBThread();
}

private
void startCopyFileThread(){ Log.d(TAG, "startCopyDBThread"); new Thread(new Runnable() { @Override public void run() { //DO SOMETHING LIKE: copyDBFile(); Message msg=mHandler.obtainMessage(); mHandler.sendMessage(msg); } }).start(); } private static class CopyFileHandler extends Handler { WeakReference<AppStartActivity> mActivity; public CopyFileHandler(AppStartActivity activity) { mActivity = new WeakReference<>(activity); } public void handleMessage(Message msg) { final AppStartActivity activity = mActivity.get(); //handle you message here! } }

為什麽會內存泄漏

那麽為什麽不這樣做會引發內存泄漏呢?
這與幾個關鍵詞有關:內部類、Handler的消息循環(Looper)、Java垃圾回收機制。
須要強調一下,並非每次使用Handler都會引發內存泄漏。這裏面有一定的幾率,須要滿足特定條件才會引起泄漏。


內部類會有一個指向外部類的引用。
垃圾回收機制中約定。當內存中的一個對象的引用計數為0時。將會被回收。
Handler作為Android上的異步消息處理機制(好吧,我大多用來進行worker thread與UI線程同步),它的工作是須要Looper和MessageQueue配合的。簡單的說,要維護一個循環體(Looper)處理消息隊列(MessageQueue)。

每循環一次就從MessageQueue中取出一個Message。然後回調對應的消息處理函數。

假設,我是說假設,循環體中有消息未處理(Message排隊中),那麽Handler會一直存在。那麽Handler的外部類(一般是Activity)的引用計數一直不會是0,所以那個外部類就不能被垃圾回收。

非常多人會遇到activity的onDestroy方法一直不運行就是這個原因。

還有一個解決方式的嘗試

警告描寫敘述中提到了Handler在worker thread中使用Looper或MessageQueue,我嘗試了一下。請大家品鑒。

    private Handler testHandler;
    private Thread mThread = new Thread() {
        public void run() {
            Log.d(TAG,"mThread run");
            Looper.prepare();
            testHandler = new Handler() {
                public void handleMessage(Message msg) {
                    Log.d("TAG", "worker thread:"+Thread.currentThread().getName());
                    switch (msg.what) {
                        //handle message here
                    }
                }
            };
            Looper.loop();
        }
    };

    //start thread here
    if(Thread.State.NEW == mThread.getState()) {
        Log.d(TAG, "mThread name: " + mThread.getName());
        mThread.start();
    }

    //send message here
    testHandler.sendEmptyMessage(1);

參考:
http://stackoverflow.com/questions/11407943/this-handler-class-should-be-static-or-leaks-might-occur-incominghandler
http://m.blog.csdn.net/blog/wurensen/41907663
http://blog.csdn.net/lmj623565791/article/details/38377229

Android實戰技巧之三十八:Handler使用中可能引發的內存泄漏