写在前面
最近公司变动频繁,心下略慌,刚才随便逛看到2个面试题:
一个线程有几个Handler,如果一个线程有多个Handler,那么怎么系统怎么确定某个Message所回调到的handleMessage方法
两个副线程可以使用Looper.prepare公用一个MessageQueue吗?如果可以怎么实现
下意识给出如下答案:
- 一个线程可以有多个Handler。Handler在发送Message的时候会通过msg.target=this来把Handler和该Handler发送的消息关联起来。Message在被Looper提取处理的时候,会调用Message对应的tagert的dispatchMessage来,从而实现多个Handler回调的准确性。
- 第一反应是不能的,首先Looper.prepare的方法是通过ThreadLocal来保证多线程的互不影响,也保证了Looper在每个线程中都是唯一的,他这个题目我首先就有点看不懂了。最后看他说,如果可以,怎么实现?兄弟,貌似不可以啊,如果不可以为什么还要问怎么实现?一点思路都没有,又一想到Handler这个源码每次都是看了忘,遂再次走读源码并做个笔记。
Looper
1 | private static void prepare(boolean quitAllowed) { |
ThreadLocal
暂时不纠结源码,反正被他修饰的变量都会为每个线程拷贝一份副本,使得多线程可以无冲突的访问同一个变量且互相不影响。所以这个就保证了上面的prepare方法只会被调用1次,以及每个线程有且只有一个Looper。
获取消息
1 | public static void loop() { |
不断的循环,调用消息队列的next方法取出消息,再调用msg.target.dispatchMessage
,即Handler的dispatchMessage。
Handler
1 | public Handler(Looper looper, Callback callback, boolean async) { |
CallBack
1 | public interface Callback { |
没啥用,在处理的时候发现如果有Callback就调用Callback的handleMessage,就是不想写个类来实现自己的Handler的话使用这个回调来处理Message。
发送
1 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { |
sendMessage
主要设置了msg.target=当前的handler 以及调用MessageQueue来enqueue消息,后续会被Looper获取。
1 | public final boolean post(Runnable r) |
而post
是将传入的参数r作为新建的Message的成员callback,在后续的处理中会优先被调用到。
处理
1 | public void dispatchMessage(Message msg) { |
在这里先判断msg.callback是否为null,如果我们调用的Handler.post方法,那么这里就会执行handleCallback
,否则再查看Handler.Callback是否为null,不为null则通过CallBack来处理本次事件,否则调用我们重写的handleMessage。
HandlerThread
1 | //创建子线程 |
内存泄露
[update:2018-02-07 10:36:06 ——————————————————]
不是说匿名内部类持有外部引用就会导致内存泄漏,而是因为他们的生命周期不同步。
message.target持有Handler,而Handler又持有Activity引用。 CallBack也一样,是Handler的成员,所以之前说的使用CallBack的方法根本不行,实际测试也是会导致内存泄露。
[update:2018-02-07 10:36:06 ——————————————————]
通常我们在Activity中写Handler的时候都是如下的形式:
1 | Handler handler = new Handler(){ |
ide这个时候会报一个内存泄露的警告,为什么呢?
上面说过message.target就是发送该消息的Handler,而上述的Handler是Activity的一个匿名类,java中匿名类和non-static内部类会持有外部类的引用,即Activity。假设Activity被关闭了(注意区分Activity生命周期和主线程的生命周期),但是MessageQueue中的消息还没被处理,那么消息队列中的message就会持有这个Handler的引用,Handler又持有Activity的引用,导致Activity无法被gc回收,导致内存泄漏。
通过CallBack,来避免泄露的写法: 这是错误的,正确用法是通过弱引用,详见参考
1 | Handler handler = new Handler(new Handler.Callback() { |
通过静态内部类,也是官方推荐的:
1 | private static class MyHandler extends Handler { |