1. 程式人生 > >優雅地使用Handler,避免記憶體溢位、空指標

優雅地使用Handler,避免記憶體溢位、空指標

在Activity中直接建立Handler的內部類,比如這樣:

public class HandlerActivity extends AppCompatActivity {

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            doSomething();
        }
    };

    private void doSomething() {}

}
這時IDE會有黃色的提示:
This Handler class should be static or leaks might occur (null) less... (Ctrl+F1)

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.

非靜態的內部類會隱式地持有外部類的引用,這會導致外部類物件無法被系統的GC回收。
如果你的Handler是在主執行緒外的執行緒建立的,那就沒問題。
如果你的Handler就是在主執行緒建立的,那就應該使用靜態內部類+弱引用的方式來持有外部類引用。
舉個例子:
你的Handler在子執行緒執行了耗時操作再sendMessage,或者傳送的Message直接就是延時執行的,如:
mHandler.sendEmptyMessageDelayed(1,5000);//5秒後執行
如果這時候,使用者按了返回鍵,撤銷了這個Activity,就會可能導致記憶體洩漏,因為mHandler持有了Activity的引用,而Message又持有了Handler的引用,這個Message在MessageQueue中佇列,直到時間到了才會被handleMessage。而在這段時間內,Activity是無法被GC回收掉的。

根據提示,修改如下:
public class HandlerActivity extends AppCompatActivity {

    private SafeHandler mSafeHandler = new SafeHandler(this);

    /**根據提示實現的Handler*/
    private static class SafeHandler extends Handler {

        private WeakReference<HandlerActivity> mWeakReference;

        public SafeHandler(HandlerActivity activity) {
            super();
            mWeakReference = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = mWeakReference.get();
            if (activity != null) {
                activity.doSomething();
            }
        }
    }

    private void doSomething() {}

}

通過靜態內部類+弱引用來持有物件,確保Activity能被及時回收掉,在handleMessage時獲取物件,再先判空操作,防止空指標異常。

雖然重複程式碼只有幾行,不過還是可以抽取為基類的,使用的時候依然是採用靜態內部類來繼承基類:

/**
 * Handler的封裝
 * 子類應為靜態內部類
 *
 * @param <T> 外部類
 */
public abstract class BaseHandler<T>
        extends Handler {
    
    private final WeakReference<T> mReference;
    
    public BaseHandler(T t) {
        super(Looper.getMainLooper());
        mReference = new WeakReference<>(t);
    }
    
    @Override
    public void handleMessage(Message msg) {
        T t = mReference.get();
        if (t != null) {
            handleMessage(t, msg);
        }
    }
    
    protected abstract void handleMessage(T t, Message msg);
}

外部類使用時,如下所示:

public class MainActivity
        extends CoreActivity {
    
    private Handler mHandler = new MyHandler(this);
    
    private static final class MyHandler
            extends BaseHandler<MainActivity> {
        
        public MyHandler(MainActivity mainActivity) {
            super(mainActivity);
        }
        
        @Override
        protected void handleMessage(MainActivity mainActivity, Message msg) {
            
        }
    
}

注:測試時會發現Activity總是不為NULL,那是因為系統沒有呼叫GC回收。
使用DDMS主動呼叫幾次Cause GC就行了。