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
, 因為MessageQueue
是Looper
共用的,
加這個判斷條件就只會清除這個handler 例項的任務,而不影響其他 handler 例項的任務。
所以只需要在一個 ui 元件內使用一個 handler 例項,在 ui 銷燬的時候,執行 handler.removeCallbacksAndMessages
。
例如 Activity, Fragment onDestroy
, View onDetachedFromWindow
的時候。
所以,每個ui 元件都應該有一個 handler 例項,這個handler的生命週期跟隨ui 元件的生命週期,用於執行依賴此ui元件的任務,
而不是使用一個全域性的Handler,一般全域性的Handler只適用於往某個Looper執行緒提交與當前例項無依賴的任務。