线程启动之后,进入main()方法,在main()方法中进行线程的一些初始化,初始化工作完成之后,会调用Looper.loop()进行消息监听,而loop()方法是一个死循环,从而保证线程不会立即退出:
在loop()方法中,当有消息的时候,就会从queue.next()方法中读取到最新可用消息,通过dispatch进行消息分发处理,当queue中没有可用消息的时候,整个方法就会阻塞在queue.next()方法的位置,直到继续有可用消息到来。
1. 为什么主线程不会因为Looper.loop()中的死循环卡死?
主线程是通过Looper.loop()方法进入了循环状态,因为这样主线程才不会像我们创建的一般线程那样,当可执行代码结束之后,线程的生命周期就结束退出了。
当主线程的MessageQueue中没有消息的时候,会阻塞在Messagequeue.next()方法中的nativePollOnce()方法中,此时主线程会释放CPU资源进入休眠状态,直到新消息到来的时候。所以主线程大部分时间是处于休眠状态的,并不会消耗大量的CPU资源。
这里采用的是Linux的epoll机制,通过监控文件描述符eventfd,当消息队列MessageQueue中有可执行消息的时候,同时会向eventfd中写入数据,从而唤醒主线程进行消息的分发处理。
2. post和sendMessage两个发送消息方法的区别?
post方法发送的Runnable对象,最后会被封装成一个Message对象,Runnable会被赋值给Message对象的callback变量,然后通过sendMessageAtTime()方法发送出去,在分发处理消息的时候,在dispatchMessage()方法中,要是当前Message的callback变量不为空,则会优先选择执行callback的run()方法,其实就是运行了Runnable。
而sendMessage发送的就是一个封装好的Message对象,该消息的callback一般是空的,在dispatchMessage的时候,因为callabck为空,所以分发消息的优先级比较低,是通过执行Message的target变量(target其实就是Handler对象自己)的handleMessage()方法执行任务的,所以我们在使用Handler的sendMessage()方法发送消息的时候,需要同时重些Handler的handlerMessage()方法。
3. 在构建Message对象的时候,为什么建议通过Message.obtain()方法获取Message对象?
obtain()方法可以从全局消息池中获得一个空的Message对象,这样可以有效的减少Message对象创建时消耗的系统资源。同时,通过obtain()的重载方法还可以获得一些Message的拷贝,或对Message对象进行一些初始化。
4. Handler发送延迟消息的原理是什么?
我们通过postDelayed()和sendMessageDelayed()方法来发送延迟消息,其实最终是通过方法中指定的延时时长 当前时间戳,计算出来该消息确定的分发时间,然后通过sendMessageAtTime()方法插入到MessageQueue中,等待到这个时间点之后才会分发处理该消息,所以延迟消息是立即发送,然后延后分发处理。
消息延迟的原理是,首先Looper在发送消息到MessageQueue的时候,会按照消息设定的分发时间先后排序放在链表中,然后通过nativePollOnce()方法让线程在休眠一段时间,等到第一个消息的处理时间到达的时候,唤醒线程处理消息,从而实现延迟消息。
5. 同步屏障SyncBarrier是什么,作用是什么?
我们一般用到Handler消息是同步消息,其实Handler有三种消息:同步消息、异步消息和屏障消息SyncBarrier,屏障消息也会被插入到MessageQueue中。同步消息和异步消息的target在传入MessageQueue的时候会保证不为空,以便与在消息分发的时候知道该消息应该分发给谁,而屏障消息的target是空的,这也是Handler中判断一个消息是否为屏障消息的标准。MessageQueue.next()方法中如果当前消息是屏障消息,则会跳过后面所有的同步消息,找到屏障消息后第一个异步消息进行分发处理。
需要注意的是开发者没有办法直接向MessageQueue中插入消息屏障(当然通过反射机制是可以的)。
在View绘制的时候,View绘制的Message是优先于其他的消息,在有View绘制消息的时候,系统会同时向MessageQueue中插入一个消息屏障,从而使该消息优先处理,避免耗时的Message阻塞UI的绘制。
6. IdleHandler是什么?有什么作用 ?
当消息队列没有可用消息的时候,线程会进入休眠状态,这种场景我们可以通过IdleHandler来监听线程进入休眠状态,具体的使用方法如下:
App性能监控LeakCanary为了避免监控程序对App运行时资源的抢占,就会利用IdleHandler来监控主线程的休眠情况,在主线程处于休眠的时候,才会跟踪监控对象的内存泄漏,这样避免LeakCanary对UI绘制这样的优先级较高的Message的影响。
7. 为什么非静态类的Handler会导致内存泄漏,如何解决?
在Activity中创建Handler,Handler会持有Activity的引用,通过Handler发送的消息Message中的target就是Handler对象,因此MessageQueue中有消息未处理的时候,MessageQueue中会持有Handler的引用,而MessageQueue的生命周期贯穿了主线程的生命周期,要比Activity的生命周期长,只要MessageQueue中还有在Activity中创建的Message的时候,那么Handler就无法被回收,从而导致Activity的引用也一直被Handler持有而Activity无法被回收,造成内存泄漏。
解决的办法就是使用静态内部类 弱引用的方式:
然后将Activity中创建MyHandler对象设置为static。
8. 如何在子线程中弹窗Toast
在子线程中调用Looper.prepare()方法,并调用Looper.loop()方法,这样就会在子线程中创建一个Looper对象和MessageQueue消息队列,而loop()让当前子线程开始监听消息,这样我们在子线程中显示Toast的时候,UI绘制的消息才会发送到子线程的队列中,在消息分发的时候进行UI绘制。
但是需要注意的是,任务执行完毕之后,需要手动调用Looper.quitSafely()方法退出循环,否则子线程一直不会结束退出。