写点什么

Android 全面解析之 Handler 机制:常见问题汇总,kotlin 静态类

用户头像
Android架构
关注
发布于: 刚刚

Handler 是如何切换线程的?

答: 使用不同线程的 Looper 处理消息。


前面我们聊到,代码的执行线程,并不是代码本身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个 Looper 都运行在对应的线程,所以不同的 Looper 调用的 dispatchMessage 方法就运行在其所在的线程了。

Handler 的阻塞唤醒机制是怎么回事?

答: Handler 的阻塞唤醒机制是基于 Linux 的阻塞唤醒机制。


这个机制也是类似于 handler 机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。

能不能让一个 Message 加急被处理?/ 什么是 Handler 同步屏障?

答:可以 / 一种使得异步消息可以被更快处理的机制


如果向主线程发送了一个 UI 更新的操作 Message,而此时消息队列中的消息非常多,那么这个 Message 的处理就会变得缓慢,造成界面卡顿。所以通过同步屏障,可以使得 UI 绘制的 Message 更快被执行。


什么是同步屏障?这个“屏障”其实是一个 Message,插入在 MessageQueue 的链表头,且其 target==null。Message 入队的时候不是判断了 target 不能为 null 吗?不不不,添加同步屏障是另一个方法:


public int postSyncBarrier() {


return postSyncBarrier(SystemClock.uptimeMillis());


}


private int postSyncBarrier(long when) {


synchronized (this) {


final int token = mNextBarrierToken++;


final Message msg = Message.obtain();


msg.markInUse();


msg.when = when;


msg.arg1 = token;


Message prev = null;


Message p = mMessages;


// 把当前需要执行的 Message 全部执行


if (when != 0) {


while (p != null && p.when <= when) {


prev = p;


p = p.next;


}


}


// 插入同步屏障


if (prev != null) { // invariant: p == prev.next


msg.next = p;


prev.next = msg;


} else {


msg.next = p;


mMessages = msg;


}


return token;


}


}


可以看到同步屏障就是一个特殊的 target,哪里特殊呢?target==null,我们可以看到他并没有给 target 属性赋值。那这个 target 有什么用呢?看 next 方法:


Message next() {


...


// 阻塞时间


int nextPollTimeoutMillis = 0;


for (;;) {


...


// 阻塞对应时间


nativePollOnce(ptr, nextPollTimeoutMillis);


// 对 MessageQueue 进行加锁,保证线程安全


synchronized (this) {


final long now = SystemClock.uptimeMillis();


Message prevMsg = null;


Message msg = mMessages;


/**


  • 1


*/


if (msg != null && msg.target == null) {


// 同步屏障,找到下一个异步消息


do {


prevMsg = msg;


msg = msg.next;


} while (msg != null && !msg.isAsynchronous());


}


if (msg != null) {


if (now < msg.when) {


// 下一个消息还没开始,等待两者的时间差


nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);


} else {


// 获得消息且现在要执行,标记 MessageQueue 为非阻塞


mBlocked = false;


/**


  • 2


*/


// 一般只有异步消息才会从中间拿走消息,同步消息都是从链表头获取


if (prevMsg != null) {


prevMsg.next = msg.next;


} else {


mMessages = msg.next;


}


msg.next = null;


msg.markInUse();


return msg;


}


} else {


// 没有消息,进入阻塞状态


nextPollTimeoutMillis = -1;


}


// 当调用 Looper.quitSafely()时候执行完所有的消息后就会退出


if (mQuitting) {


dispose();


return null;


}


...


}


...


}


}


这个方法我在前面讲过,我们重点看一下关于同步屏障的部分,看注释 1 的地方的代码:


if (msg != null && msg.target == null) {


// 同步屏障,找到下一个异步消息


do {


prevMsg = msg;


msg = msg.next;


} while (msg != null && !msg.isAsynchronous());


}


如果遇到同步屏障,那么会循环遍历整个链表找到标记为异步消息的 Message,即 isAsynchronous 返回 true,其他的消息会直接忽视,那么这样异步消息,就会提前被执行了。注释 2 的代码注意一下就可以了。


注意,同步屏障不会自动移除,使用完成之后需要手动进行移除,不然会造成同步消息无法被处理。从源码中可以看到如果不移除同步屏障,那么他会一直在那里,这样同步消息就永远无法被执行了。


有了同步屏障,那么唤醒的判断条件就必须再加一个:MessageQueue 中有同步屏障且处于阻塞中,此时插入在所有异步消息前插入新的异步消息。这个也很好理解,跟同步消息是一样的。如果把所有的同步消息先忽视,就是插入新的链表头且队列处于阻塞状态,这个时候就需要被唤醒了。看一下源码:


boolean enqueueMessage(Message msg, long when) {


...


// 对 MessageQueue 进行加锁


synchronized (this) {


...


if (p == null || when == 0 || when < p.when) {


msg.next = p;


mMessages = msg;


needWake = mBlocked;


} else {


/**


  • 1


*/


// 当线程被阻塞,且目前有同步屏障,且入队的消息是异步消息


needWake = mBlocked && p.target == null && msg.isAsynchronous();


Message prev;


for (;;) {


prev = p;


p = p.next;


if (p == null || when < p.when) {


break;


}


/**


  • 2


*/


// 如果找到一个异步消息,说明前面有延迟的异步消息需要被处理,不需要被唤醒


if (needWake && p.isAsynchronous()) {


needWake = false;


}


}


msg.next = p;


prev.next = msg;


}


// 如果需要则唤醒队列


if (needWake) {


nativeWake(mPtr);


}


}


return true;


}


同样,这个方法我之前讲过,把无关同步屏障的代码忽视,看到注释 1 处的代码。如果插入的消息是异步消息,且有同步屏障,同时 MessageQueue 正处于阻塞状态,那么就需要唤醒。而如果这个异步消息的插入位置不是所有异步消息之前,那么不需要唤醒,如注释 2。


那我们如何发送一个异步类型的消息呢?有两种办法:


  • 使用异步类型的 Handler 发送的全部 Message 都是异步的

  • 给 Message 标志异步


Handler 有一系列带 Boolean 类型的参数的构造器,这个参数就是决定是否是异步 Handler:


public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {


mLooper = looper;


mQueue = looper.mQueue;


mCallback = callback;


// 这里赋值


mAsynchronous = async;


}


在发送消息的时候就会给 Message 赋值:


private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,


long uptimeMillis) {


msg.target = this;


msg.workSourceUid = ThreadLocalWorkSource.getUid();


// 赋值


if (mAsynchronous) {


msg.setAsynchronous(true);


}


return queue.enqueueMessage(msg, uptimeMillis);


}


但是异步类型的 Handler 构造器是标记为 hide,我们无法使用,所以我们使用异步消息只有通过给 Message 设置异步标志:


public void setAsynchronous(boolean async) {


if (async) {


flags |= FLAG_


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ASYNCHRONOUS;


} else {


flags &= ~FLAG_ASYNCHRONOUS;


}


}


但是!!!!,其实同步屏障对于我们的日常使用的话其实是没有多大用处。因为设置同步屏障和创建异步 Handler 的方法都是标志为 hide,说明谷歌不想要我们去使用他。所以这里同步屏障也作为一个了解,可以更加全面地理解源码中的内容。

什么是 IdleHandler?

答: 当 MessageQueue 为空或者目前没有需要执行的 Message 时会回调的接口对象。


IdleHandler 看起来好像是个 Handler,但他其实只是一个有单方法的接口,也称为函数型接口:


public static interface IdleHandler {


boolean queueIdle();


}


在 MessageQueue 中有一个 List 存储了 IdleHandler 对象,当 MessageQueue 没有需要被执行的 Message 时就会遍历回调所有的 IdleHandler。所以 IdleHandler 主要用于在消息队列空闲的时候处理一些轻量级的工作。


IdleHandler 的调用是在 next 方法中:


Message next() {


// 如果 looper 已经退出了,这里就返回 null


final long ptr = mPtr;


if (ptr == 0) {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android全面解析之Handler机制:常见问题汇总,kotlin静态类