Android-如何正確地使用Handler

xechoz發表於2018-01-03

Handler常見問題及原因

1. 記憶體洩漏

通常我們是這樣使用

handler.post(new Runnable() {
    @Override
    public void run() {
        ...
    }
});
複製程式碼

大部分情況下使用匿名內部類,如果沒有注意及時釋放,就很有可能引起記憶體洩漏,
因為匿名內部類持有了外部類的引用導致記憶體洩漏。

2. 記憶體回收導致空指標

大部分的空指標是非同步請求網路導致的,按上面的理解來說,匿名內部類持有了外部類,外部類是不應該被回收的,
但是實際情況是,Context,UI 相關的例項,在執行非同步回撥的時候,很可能被系統回收置空了,這裡也是我一直不理解的地方:
內部類間接持有的Context為什麼會為Null?

怎麼解決

記憶體洩漏通常是使用 static class 替代匿名內部類避免持有外部的類例項。
但這種用法意味者失去了匿名內部類的簡潔易用,同時也使得程式碼過於分散了,對於程式碼維護並不是一件好事。

現在目標差是,在適當的時候,移除所有post的任務,避免記憶體洩漏,空指標等
Handler 提供了一個介面removeCallbacksAndMessages

/**
* Remove any pending posts of callbacks and sent messages whose obj is token. 
* If token is null, all callbacks and messages will be removed.
**/
void removeCallbacksAndMessages (Object token)
複製程式碼

上面的程式碼最後是執行了MessageQueue.removeCallbacksAndMessages

 void removeCallbacksAndMessages(Handler h, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            // 注意這裡的 p.target == h
            while (p != null && p.target == h
                    && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            // 注意這裡的 n.target == h
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }
複製程式碼

注意上面的p.target == h 或者n.target == h, 因為MessageQueueLooper共用的,
加這個判斷條件就只會清除這個handler 例項的任務,而不影響其他 handler 例項的任務。

所以只需要在一個 ui 元件內使用一個 handler 例項,在 ui 銷燬的時候,執行 handler.removeCallbacksAndMessages
例如 Activity, Fragment onDestroy, View onDetachedFromWindow 的時候。

所以,每個ui 元件都應該有一個 handler 例項,這個handler的生命週期跟隨ui 元件的生命週期,用於執行依賴此ui元件的任務,
而不是使用一個全域性的Handler,一般全域性的Handler只適用於往某個Looper執行緒提交與當前例項無依賴的任務。

相關文章