handler 核心问题
Handler 核心问题
1.为什么主线程不会因为 Looper.loop() 里的死循环卡死?
主线程确实是通过 Looper.loop() 进入了循环状态,因为这样主线程才不会像我们一般创建的线程一样,当可执行代码执行完后,线程生命周期就终止了。
在主线程的 MessageQueue 没有消息时,便阻塞在 MessageQueue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到新消息到达。
所以主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。这里采用的 Linux epoll 机制,是一种 IO 多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作拿到最新的消息,进而唤醒等待的线程。
2.post 和 sendMessage 两类发送消息的方法有什么区别?
post 一类的方法发送的是 Runnable 对象,但是其最后还是会被封装成 Message 对象,将 Runnable 对象赋值给 Message 对象中的 callback 变量,然后交由 sendMessageAtTime() 方法发送出去。
在处理消息时,会在 dispatchMessage() 方法里优先走 handleCallback(msg) 这条分支,实际上就是执行 Message 对象里面的 Runnable 对象的 run 方法。
而 sendMessage 一类的方法发送的直接是 Message 对象。处理消息时,如果 msg.callback 为空,则会继续判断是否设置了 Handler.Callback;如果也没有,最终才会回调我们重写的 handleMessage(msg) 方法。
3.为什么要通过 Message.obtain() 方法获取 Message 对象?
obtain() 方法可以从全局消息池中得到一个空的 Message 对象,这样可以有效节省系统资源。
同时,通过各种 obtain 重载方法还可以得到一些 Message 的拷贝,或对 Message 对象进行一些初始化。
4.Handler 实现发送延迟消息的原理是什么?
我们常用 postDelayed() 与 sendMessageDelayed() 来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过 sendMessageAtTime() -> enqueueMessage() -> queue.enqueueMessage() 这一系列方法将消息插入到 MessageQueue 中。
所以并不是先延迟再发送消息,而是直接发送消息,再借助 MessageQueue 的设计来实现消息的延迟处理。
消息延迟处理的原理涉及 MessageQueue.enqueueMessage() 和 MessageQueue.next() 这两个实例方法。
消息入队时会按 when 时间有序插入 MessageQueue。Looper 在循环中调用 MessageQueue.next() 取消息时,如果队首消息还没到执行时间,就会在 Native 层等待相应时长;等到执行时间到达后,再把消息取出并分发执行。
5.同步屏障 SyncBarrier 是什么?有什么作用?
在一般情况下,同步和异步消息处理起来没有什么不同。只有在设置了同步屏障后才会有差异。同步屏障从代码层面上看是一个 Message 对象,但是其 target 属性为 null,用以区分普通消息。
在 MessageQueue.next() 中如果当前消息是一个同步屏障,则会暂时跳过后续同步消息,优先找到第一个可执行的异步消息来处理。
普通应用开发中没有公开 SDK API 直接操作同步屏障。在 ViewRootImpl 的 UI 刷新流程中可以看到它的使用场景。
6.IdleHandler 是什么?有什么作用?
当消息队列没有消息时调用,或者队列中虽然仍有待处理消息,但都还没到执行时间时,也会调用此方法。它用于监听某个 Looper 对应线程的空闲时机,主线程只是最常见的使用场景。
7.为什么非静态类的 Handler 会导致内存泄漏?如何解决?
首先,非静态的内部类、匿名内部类、局部内部类都会隐式地持有其外部类的引用。也就是说,在 Activity 中创建的非静态 Handler 会因此持有 Activity 的引用。
当我们在主线程使用 Handler 的时候,Handler 会默认绑定这个线程的 Looper 对象,并关联其 MessageQueue,Handler 发出的所有消息都会加入到这个 MessageQueue 中。
Looper 对象的生命周期贯穿整个主线程的生命周期,所以当 MessageQueue 中还存在未处理完的延迟消息、长时间排队的消息或 Runnable 时,由于 Message 会持有 Handler 的引用,Handler 又持有外部 Activity 的引用,就可能导致 Activity 在本应销毁后仍无法及时回收,从而造成内存泄漏。
解决方式:使用静态内部类 + 弱引用。
8.如何在子线程中弹出 Toast?
更推荐的做法是切回主线程再弹出 Toast,例如使用 Handler(Looper.getMainLooper())、Activity.runOnUiThread(),或者其他主线程调度方式。
如果强行在子线程中调用 Looper.prepare() 和 Looper.loop(),本质上是给这个线程创建了自己的消息循环;但 Looper.loop() 会持续进入消息分发循环,必须有明确的退出时机,否则线程不会结束。这种写法一般不作为应用层的常规方案。
Enjoy Reading This Article?
Here are some more articles you might like to read next: