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线程提交与当前实例无依赖的任务。