Android-如何正确地使用Handler

2,044 阅读2分钟

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