Android筑基——可视化方式理解 Handler 的同步屏障机制

目录1 前言2 正文2.1 效果展示2.2 代码讲解2.2.1 获取独立的消息队列2.2.2 发送同步消息2.2.3 发送异步消息2.2.4 发送同步屏障消息2.2.5 移除同步消息屏障2.2.6 显示消息队列的内容3 最后参考

1 前言

网上有不少文章深入源码讲解 Handler 的同步屏障机制;也有一些作者贴心地举出了实际生活中的例子来帮助理解 Handler 的同步屏障机制。这都有助于大家理解 Handler 的同步屏障机制。

本文打算以可视化的方式展示 Handler 的同步屏障机制,更加直观地帮助大家理解 Handler 的同步屏障机制。

2 正文
2.1 效果展示

只有同步消息的队列表现
Android筑基——可视化方式理解 Handler 的同步屏障机制
可以看到在只有同步消息时,消息按照优先级顺序按序被依次取出。
需要说明的是,优先级顺序在本文中和发送消息的顺序是一致的,因为发送消息之间的时间间隔总是 1000 毫秒。
只有异步消息的队列表现
Android筑基——可视化方式理解 Handler 的同步屏障机制
可以看到在只有异步消息时,消息同样是按照优先级顺序按序被依次取出。
同时有同步消息和异步消息的队列表现
Android筑基——可视化方式理解 Handler 的同步屏障机制
可以看到在有同步消息和异步消息时,消息同样是按照优先级顺序按序被依次取出,完全体现不出同步消息和异步消息有什么区别。
有同步屏障时同步消息和异步消息的队列表现
Android筑基——可视化方式理解 Handler 的同步屏障机制
采取的动作:先发送了一个同步屏障消息,再间隔各发送了 5 个同步消息和异步消息。
效果:异步消息被按照优先级顺序一一取出,同步消息没有被处理,按照优先级顺序留在了队列里面。
移除同步屏障时同步消息的队列表现
Android筑基——可视化方式理解 Handler 的同步屏障机制
采取的动作:先发送一个同步屏障消息,再发送 6 个同步消息;随后,移除同步屏障消息。
效果:在移除同步屏障消息之后,同步消息按照优先级顺序依次取出。

2.2 代码讲解

效果演示结束,下面我们开始介绍代码。

2.2.1 获取独立的消息队列

我们希望有一个独立的消息队列,即 MessageQueue 对象,为什么呢?因为我们希望消息队列只会被我们操作,而不会收到其他来源的消息,或者被其他地方从消息队列里取出消息。

那么,怎样才能有一个独立的 MessageQueue 对象呢?自然是需要 new 出一个了,而 newMessageQueue 的地方是在 Looper 的构造方法里面:

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

这个方法又被 Looper.prepare() 方法所调用:

public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

当然,我们不能在主线程里调用 Looper.prepare(),因为主线程已经有 Looper 对象了,所以我们需要开启一个新的线程,在里面调用 Looper.prepare() 方法,再调用 Looper.loop() 方法开始从队列中取消息。

Thread {
Looper.prepare()
val handler1 = Handler(Looper.myLooper())
Looper.loop()
}.start()

而通过 Handler 对象,可以获取到 MessageQueue 对象:

val messageQueue = handler.looper.queue

实际上,可以直接用 HandlerThread 来完成上面创建线程,再创建 Handler 的工作:

private fun createHandler() {
handlerThread = HandlerThread(TAG)
handlerThread.start()
handler = MyHandler(handlerThread.looper)
}

2.2.2 发送同步消息

private fun sendSyncMessage() {
syncMessageCounter++
val message = Message.obtain()
message.what = MESSAGE_CODE
val content = "同步消息 $syncMessageCounter"
message.obj = content
handler.sendMessageDelayed(message, 1000L * (syncMessageCounter + asyncMessageCounter))
Log.d(TAG, "sendSyncMessage: 发送 $content")
printMessageQueue()
}

2.2.3 发送异步消息

private fun sendAsyncMessage() {
asyncMessageCounter++
val message = Message.obtain()
message.what = MESSAGE_CODE
val content = "异步消息 $asyncMessageCounter"
message.obj = content
message.isAsynchronous = true
handler.sendMessageDelayed(message, 1000L * (syncMessageCounter + asyncMessageCounter))
Log.d(TAG, "sendAsyncMessage: 发送 $content")
printMessageQueue()
}

注意,与发送同步消息的区别在于:message.isAsynchronous = true

2.2.4 发送同步屏障消息

@SuppressLint("SoonBlockedPrivateApi")
private fun sendSyncBarrierMessage() {
try {
val messageQueue = handler.looper.queue
val postSyncBarrierMethod =
MessageQueue::class.java.getDeclaredMethod("postSyncBarrier", Long::class.java)
postSyncBarrierMethod.isAccessible = true
val uptimeMillis = SystemClock.uptimeMillis()
Log.d(TAG, "sendSyncBarrierMessage: uptimeMillis=$uptimeMillis")
val tokenObj = postSyncBarrierMethod.invoke(messageQueue, uptimeMillis)
if (tokenObj is Int) {
val token = tokenObj as Int
tokenList.add(token)
Log.d(TAG, "sendSyncBarrierMessage: token=$token")
}
printMessageQueue()
} catch (e: Exception) {
Log.e(TAG, "sendSyncBarrierMessage: ", e)
}
}

这里通过反射调用了 MessageQueue 类的 postSyncBarrier(long when) 方法:

private int postSyncBarrier(long when) {
}

使用集合存储了 postSyncBarrier 方法的返回值,token,之后会作为移除同步屏障消息 removeSyncBarrier(int token) 的参数。

需要注意的是,这个 api 在高版本是不允许反射调用的,测试发现:

在 Xiaomi Redmi Note 8 Pro Android 9, API 28 上,报错:Accessing hidden method Landroid/os/MessageQueue;->postSyncBarrier(J)I (dark greylist, reflection)
在 HUAWEI HRY-AL00a Android 10, API 29 上, 报错:Accessing hidden method Landroid/os/MessageQueue;->postSyncBarrier(J)I (greylist-max-o, reflection, denied)

所以,大家使用代码时需要注意到这一点,使用低版本的机型就可以了。我这边测试是在 Motorola XT1650 Android 8.0.0, API 26 是可以的。

2.2.5 移除同步消息屏障

private fun removeSyncBarrierMessage() {
try {
val messageQueue = handler.looper.queue
val removeSyncBarrierMethod =
MessageQueue::class.java.getDeclaredMethod("removeSyncBarrier", Int::class.java)
removeSyncBarrierMethod.isAccessible = true
val token = tokenList.removeLastOrNull()
Log.d(TAG, "removeSyncBarrierMessage: token=$token")
if (token != null) {
removeSyncBarrierMethod.invoke(messageQueue, token)
printMessageQueue()
}
} catch (e: Exception) {
Log.e(TAG, "removeSyncBarrierMessage: ", e)
}
}

2.2.6 显示消息队列的内容

我们知道,消息队列是一个单链表实现的队列。那么,只要我们拿到队列的头部,依次进行遍历直到队列的尾部,就可以获取到队列中每一个 Message 对象;然后,再获取 Message 对象的信息。这样,我们就能掌握到消息队列的内部情况了。

打开 MessageQueue 的代码:

public final class MessageQueue {
@UnsupportedAppUsage
Message mMessages;
}

找到 mMessages 就是队头了,依然是使用反射的方式获取它。注意,队头有可能是 null

那么怎么获取下一个节点呢?我们要看一下 Message 类,这就是节点类,它里面有一个 next 属性:

public final class Message implements Parcelable {
@UnsupportedAppUsage
/*package*/ Message next;
}

指向的就是下一个节点,仍然是通过反射获取到 next

private fun printMessageQueue() {
try {
Log.d(TAG, "printMessageQueue: ---------------------start")
val messageQueue = handler.looper.queue
val mMessagesField = MessageQueue::class.java.getDeclaredField("mMessages")
mMessagesField.isAccessible = true
val mMessages = mMessagesField.get(messageQueue) as? Message
var message: Message? = mMessages
if (mMessages == null) {
Log.d(TAG, "printMessageQueue: no message")
binding.tv.post { binding.tv.text = "no message" }
clearCounter()
return
}
val sb = StringBuilder()
while (message != null) {
val printMessage = printMessage(message)
sb.append(printMessage).append("\n")
Log.d(TAG, "printMessageQueue: $printMessage")
val nextField = Message::class.java.getDeclaredField("next")
nextField.isAccessible = true
message = nextField.get(message) as? Message
}
binding.tv.post { binding.tv.text = sb.toString() }
Log.d(TAG, "printMessageQueue: ---------------------end")
} catch (e: Exception) {
Log.e(TAG, "printMessageQueue: ", e)
}
}
private fun printMessage(message: Message): String {
val b = StringBuilder()
b.append("{ ")
if (message.target != null) {
if (message.obj != null) {
b.append(" obj=")
b.append(message.obj)
}
b.append(" target=")
b.append(message.target.javaClass.simpleName)
} else {
b.append(" 同步屏障消息 ${message.arg1}")
}
b.append(" }")
return b.toString()
}

3 最后

本文通过可视化的方式进行演示,可以直观地看到同步屏障机制的工作过程。

代码已经上传到 github 上:https://github.com/jhwsx/BlogCodes/tree/master/HandlerStudy。

另外,代码里还演示了通过监听主线程消息队列,展示了点击 Button 这样简单的操作,也是通过 Handler 的消息机制来完成的。大家如果感兴趣可以去查看一下代码。

参考

每日问答 Handler应该是大家再熟悉不过的类了,那么其中有个同步屏障机制,你了解多少呢?;
消息同步屏障;
揭秘 Android 消息机制之同步屏障:target==null ?;
Android消息机制1-Handler(Java层)。

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

Android筑基——可视化方式理解 Handler 的同步屏障机制

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » Android筑基——可视化方式理解 Handler 的同步屏障机制
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏