基本流程
Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper循环读取Message,处理Message,然后调用Message的target,即附属Handler的dispatchMessage方法,将该消息回调到handleMessage方法中,然后完成UI更新。
Handler
负责发送处理消息
Message
消息
MessageQueue
消息队列,内部使用一个Message链表实现消息的存取
Looper
不断循环执行 (Looper.loop) ,按分发机制将消息分发给目标处理者。
每个线程只存在一个Looper对象,Looper构造函数中创建了MessageQueue对象,因此一个线程只有一个MessageQueue,但是可以有多个Handler。
常见问题
- 一个线程几个Handler ?
多个,通常我们开发过程中就会new出不止一个Handler。
- 一个线程有几个Looper?如何保证?
仅有1个Looper
Looper的构造是私有的,只有通过其prepare()方法构建出来,当调用了Looper的prepare()方法后,会调用ThreadLocal中的get()方法检查ThreadLocalMap中是否已经set过Looper?
如果有,则会抛出异常,提示每个线程只能有一个Looper,如果没有,则会往ThreadLocalMap中set一个new出来的Looper对象。
这样可以保证ThreadLocalMap和Looper一一对应,即一个ThreadLocalMap只会对应一个Looper。而这里的ThreadLocalMap是在Thread中的一个全局变量,也只会有一个,所以就可以保证一个Thread中只有一个Looper。
- Handler内存泄漏的原因?
内部类持有外部的引用。
Handler原理:由于Handler可以发送延迟消息,所以为了保证消息执行完毕后,由同一个Handler接收到,所以发送出去的Message中会持有Handler的引用,这个引用存在Message的target字段中,是Handler所有的sendMessage()方法最后都会调用enqueueMessage(),而在enqueueMessage()中会给Message的target字段赋值this。
因此Message持有Handler的引用,Handler又持有Activity的引用,所以在Message处理完之前,如果Activity被销毁了,就会造成内存泄漏。
- 为何主线程可以new Handler?如果想要在子线程中new Handler要做些什么准备?
因为在ActivityThread中的main()已经对Looper进行了prepar()操作,所以可以直接在主线程new Handler()。
如果想在子线程中new Handler,则需要先手动调用Looper的prepare()方法初始化Looper,再调用Looper的loop()方法使Looper运转。
- 使用Message时应该如何创建它?
使用Message的obtain()方法创建,直接new出来容易造成内存抖动。
内存抖动是由于频繁new对象,gc频繁回收导致,而且由于可能被别的地方持有导致无法及时回收所以会导致内存占用越来越高。
使用obtain()对内存复用,可以避免内存抖动的发生。其内部维护了一个Message池,其是一个链表结构,当调用obtain()的时候会复用表头的Message,然后会指向下一个。如果表头没有可复用的message则会创建一个新的对象,这个对象池的最大长度是50。
- 使用Handler的postDelay后消息队列会有什么变化?
如果此时消息队列为空,不会执行,会计算消息需要等待的时间,等待时间到了继续执行。
- Looper死循环为什么不会导致应用卡死?
显而易见的,主线程中如果没有looper进行循环,那么主线程一运行完毕就会退出。那么我们还能运行APP吗,显然,这是不可能的,Looper主要就是做消息循环,然后由Handler进行消息分发处理,一旦退出消息循环,那么你的应用也就退出了。
从原理角度上来说:主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从 管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。