0%

Android Handler机制

Handler消息机制是怎样的过程?

Looper.loop()流程:

  1. 通过MessageQueue.next() 取Message,没有消息就阻塞当前线程。
  2. 拿到Message返回给Looper.loop的调用处,调用Message绑定的Handler的dispatchMessage处理消息。
  3. 处理结束又回到第1步,无限循环。

每个线程都有各自的Looper,每个Looper有一个MessageQueue。

消息是链表,按时间when排序。

等待新消息阻塞线程和有新消息产生恢复线程的过程是怎样的?

调用 MessageQueue.next() 方法的时候会调用 Native 层的 nativePollOnce() 方法进行精准时间的阻塞。在 Native 层,将进入 pullInner() 方法,使用 epoll_wait 阻塞等待以读取管道的通知。如果没有从 Native 层得到消息,那么这个方法就不会返回。此时主线程会释放 CPU 资源进入休眠状态。

当我们加入消息的时候,会调用 MessageQueue.enqueueMessage() 方法,添加完 Message 后,如果消息队列被阻塞,则会调用 Native 层的 nativeWake() 方法去唤醒。它通过向管道中写入一个消息,结束上述阻塞,触发上面提到的 nativePollOnce() 方法返回,好让加入的 Message 得到分发处理。

Looper.loop()里面有死循环,为什么没有阻塞主线程?

loop()里会从消息队列取消息,取不到消息就阻塞当前线程,释放CPU给其他线程使用,有消息时会唤醒阻塞等待的线程。

IdleHandler是干什么的?

IdleHandler只有一个方法boolean queueIdle()。

此方法在MessageQueue.next()中调用,MessageQueue.next()又在Looper.loop()中调用。

queueIdle()调用时机:

当前消息队列没有可以处理的消息,进入空闲状态,在阻塞等待新的消息前(nativePollOnce方法会阻塞等待新消息),先调用IdleHandler的queueIdle()。

返回true表示下一轮处理完消息后还会回调。

返回false表示这是单次回调,这次回调后不会再回调了。

有新消息时系统会调用MessageQueue的enqueueMessage(),enqueueMessage()调用nativeWake(),唤醒线程。

可以向MessageQueue添加多个IdleHandler。

IdleHandler有什么应用场景?

可以作为View绘制完成的回调,做启动时间优化。

Activity的onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间。

Activity的onResume是在绘制View之前发生的。

因为在ActivityThread的handleResumeActivity()中,调用performResumeActivity()对应Activity的onResume(),然后调用wm.addView(decor,l)对应绘制。

postDelayed(Runnable r, long delayMillis)是如何实现的?

首先从消息池获取一个Message对象,将Runnable对象放入Message的callback属性中;

再通过当前时间和延迟时间计算Runnable执行的时间,调用MessageQueue的enqueueMessage()把消息插入消息链表,执行时间会存入Message对象的when属性中,消息链表是按Message的执行时间升序排序的,插入也会插入到符合顺序的位置。

sendMessage(Message msg)和post(Runnable r)有什么区别?

post()会将Runnable放入一个Message对象的callback属性中,还是会转换为Message,本质上没有区别,只不过post要写的参数更少,使用更方便。

使用Handler有什么要注意的?

要避免内存泄露,自定义Handler要定义静态内部类,并且用弱引用引用外部对象,避免外部对象在消息池中一直被引用而不能垃圾回收进而导致内存泄露。

quit和quitSafely的区别?

quit() 和 quitSafely() 的本质就是让消息队列的 next() 返回 null,以此来退出Looper.loop()。

quit() 调用后直接终止 Looper,不在处理任何 Message,所有尝试把 Message 放进消息队列的操作都会失败,比如 Handler.sendMessage() 会返回 false,但是存在不安全性,因为有可能有 Message 还在消息队列中没来的及处理就终止 Looper 了。

quitSafely() 调用后会在所有消息都处理后再终止 Looper,所有尝试把 Message 放进消息队列的操作也都会失败。

同步消息和异步消息有什么区别?同步屏障(Barrier)是干什么的?

Handler创建时可以传递一个async的布尔值参数,带这个参数的构造函数只有系统才能调用,我们创建Handler时async传的是false,通过Handler进行sendMessage或post都是同步消息,如果async传true,则通过Handler发送的都是异步消息。

Handler的sendMessage、post、postDelayed最终都会调用enqueueMessage,这里会判断如果构造Handler时的async传了true,就设置Message.setAsynchronous(true)。

同步消息和异步消息在没有往消息队列插入同步屏障时没有区别,插入同步屏障后,执行的优先级会有变化。

MessageQueue里的消息都是按照时间升序排序的,执行也是按时间由小到大的Message依次执行,如果有一个高优先级的消息需要立即执行,如果把新消息时间设置为当前时间,可能有好几个消息都是这样,原来的按时间顺序执行的机制就没办法保证执行的先后顺序,这就需要另外的机制来保证,即同步屏障机制。

同步屏障也是一个Message对象,但是没有target(没有绑定Handler),通过MessageQueue的postSyncBarrier(long when)方法向消息队列添加。

在MessageQueue的next()方法中,如果从消息队列取出的是同步屏障的Message(target==null),则从消息队列后面找一个异步消息来执行,如果没找到则一直阻塞等待异步消息。

应用场景:

app层无法调用同步屏障,在系统源码里有使用,如ViewRootImpl的schedualeTraversals,向MessageQueue中添加了内存屏障,保证了measure、layout、draw能够优于普通的Message而得到立即执行。

为什么 View.post 里可以拿到 View 的宽高信息呢?

View.post 和 Handler.post 的区别就是:

  1. 如果在 performTraversals 前调用 View.post,则会将消息进行保存,之后在 dispatchAttachedToWindow 的时候通过 ViewRootImpl 中的 Handler 进行调用。
  2. 如果在 performTraversals 以后调用 View.post,则直接通过 ViewRootImpl 中的 Handler 进行调用。

因为 View.post 的 Runnable 执行的时候,已经执行过 performTraversals 了,也就是 View 的 measure layout draw 方法都执行过了,自然可以获取到 View 的宽高信息了。