子线程刷UI->Barrier屏障->主线程装死->应用GG?太难了
作者:访客发布时间:2023-12-22分类:程序开发学习浏览:115
大家好,本篇文章给大家分享一个困扰我多周的问题,为了这个问题真的是天天殚精竭虑、夜不能寐,幸好最终定位到了问题原因,接下来的内容干货满满,相信能对你有所帮助。
一. 子线程能更新UI?
这是一个老生常谈的问题,对于这个问题,在这里可以下一个结论:是的,子线程能更新UI。对于有些文章不加修饰的断言“只有主线程才能更新UI”这种错误说法,读者还需谨慎,擦亮眼睛。
这里就带着大家介绍三种能在子线程更新UI的场景:
1. onResume()
生命周期前子线程更新UI
比如我们可以在onCreate()
生命周期中调用子线程更新主线程UI,因为UI刷新的校验机制是发生在ViewRootImpl
的诸如requestLayout()
等方法中,而ViewRootImpl
是在onResume()
生命周期之后才被创建的:
-
ActivityThread#handleResumeActivity()
: -
WindowManagerImpl#addView()->WindowManagerGlobal#addView()
所以在onResume生命周期之前在子线程中更新主线程UI是没有问题的。
2. 创建ViewRootImpl的子线程能更新UI
我们先了解下ViewRootImpl中的线程校验机制:
mThread
的赋值地方看一下:
也就是说UI刷新时的线程校验,只要能确保刷新UI的线程和ViewRootImpl创建的线程是同一个即可。换句话说,只要你在子线程中创建了ViewRootImpl,那么你就能在子线程刷新UI。
常见的例子就是比如在子线程中创建Dialog,就能在子线程刷新Dialog相关UI。
3. 开了硬件加速后Android O版本以上子线程能更新UI
这个就是本篇文章介绍的重点,这里以View#invalidate()
作为入口进行分析:
View#invalidate()
View#invalidateInternal()
ViewGroup#invalidateChild()
关键就是这个AttachInfo#mHardwareAccelerated
这个属性表示是否开启了硬件加速,如果开启了硬件加速,就会执行方法ViewGroup#onDescendantInvalidated()
方法,这个方法最终会执行到ViewRootImpl#onDescendantInvalidated()
方法中:
可以看到,线程检验函数checkThread
已经被注释掉了,所以开启了硬件加速,子线程更新UI不会被检测到异常,即就实现了更新主线程UI的操作。
二. 子线程更新UI有啥弊端
讨论这个话题的前提我们以上面介绍的第三种情况进行分析。
在讨论子线程更新UI的操作前,我们先简单了解下Android消息屏障的相关知识点。
消息屏障
Android消息屏障也是一种特殊的Message,其中的标识就是target属性未null,一旦消息队列中存在了这样一类消息,那么该消息之后的同步消息都会得不到执行,只会执行异步消息:
MessageQueue#next()
:
这个消息有啥用呢,比如你当前有一个优先级比较高的Message需要执行,此时你就可以插入消息屏障,然后将要执行的Message设置为异步消息,保证该消息能较早被执行。
在Android源码的场景中,也有如此应用场景,比如界面UI渲染的实现机制就是如此,保证渲染相关的Message已较高优先级被执行。
目前MessageQueue中也给我们提供了插入和移除消息屏障的方法:
MessageQueue#postSyncBarrier()
:MessageQueue#removeSyncBarrier()
主线程、子线程并发更新UI
讲完了上面的消息屏障,现在我们来开始今天的重点内容:主线程和子线程并发刷新UI的弊端。
UI的刷新入口为ViewRootImpl#scheduleTraversals()
:
先通过变量mTraversalScheduled
判断是否已经执行了UI刷新,为false执行才走到下面的逻辑中:
- 插入消息屏障;
- 注册Vsync信号执行callback;
- 注册Vsync信号监听;
这就出现了一个非常严重的主线程、子线程并发更新UI的弊端,原因在于scheduleTraversals
方法并不是线程安全的。
mTraversalScheduled
这个变量是一个线程间不可见的变量,假设当前主线程执行了一次scheduleTraversals()
方法更新UI并将mTraversalScheduled
置为true,然后发送了消息屏障,此时子线程也开始执行scheduleTraversals()
方法更新UI,但是对于子线程而言,根据JMM内存模型,它并不知道mTraversalScheduled这个已经被置为true了,在其内存缓存的副本中,mTraversalScheduled的值仍然为false。
接下来子线程也会走到if条件分支中,发送消息屏障,此时就会发生一个问题:子线程调用mHandler.getLooper().getQueue().postSyncBarrier()
发送消息屏障的返回值mTraversalBarrier
会覆盖掉主线程发送的消息屏障的返回值。
请注意,此时消息队列中就存在了两个消息屏障,然后当UI更新完毕时,会根据mTraversalBarrier
从消息队列中移除对应的消息屏障,由于子线程覆盖了主线程的mTraversalBarrier
,导致最终移除消息屏障时,只会移除掉子线程发送的消息屏障,而主线程发送的消息屏障就会一直停留在队列中。
而主线程消息队列平白无故多了一个消息屏障,那这可影响非常大了,要知道,我们平时执行的Message大多为同步消息,你这搞了个消息屏障,那就只能执行异步消息了,同步消息就只能一直停留在消息队列无法被执行,应用正常的消息调度就出现了大问题,各种事件无法被影响,各种功能逻辑无法被执行,应用表现就是黑屏或者触摸无响应GG。
三. 监听主线程消息队列屏障消息
为了能及时发现上面的问题根因,这里提供两个分析手段:
1. adb命令
借助adb shell dumpsys activity top > detail_activity.txt
指令:
这个指令能够dump出当前应用消息队列的detail。查看Detail最下面就能发现主线程消息队列的详情,是否存在遗留的消息屏障消息。
2. dump
利用消息队列提供的dump方法定时dump出主线程消息队列详情:
private Handler mMainHandler = new Handler(Looper.getMainLooper());
private void startTrace() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (mMainHandler != null) {
mMainHandler.dump(new Printer() {
@Override
public void println(String s) {
Log.d("", "startTrace dump: " + s);
}
}, "<-->");
}
try {
Thread.sleep(5000);
} catch (Exception e) {
Log.d("", "startTrace exception: " + e.getMessage());
}
}
}
}, "MessageDetails");
thread.start();
}
其中利用的源码机制为MessageQueue#dump()
:
四. 验证
基于以上分析,大家也可以简单写个代码验证下,通过反射主动发送一次消息屏障,而不主动去移除它,这时候最容易发现的就是触摸事件失灵了:
fun sendBarrier() {
val postSyncBarrier = MessageQueue::class.java.getDeclaredMethod("postSyncBarrier", Long.javaClass)
//关闭安全校验
postSyncBarrier.isAccessible = true
//发送消息屏障并获取对应token
val token = postSyncBarrier.invoke(Looper.getMainLooper().queue, SystemClock.uptimeMillis())
}
五. 参考文章
今日头条 ANR 优化实践系列 - Barrier 导致主线程假死
相关推荐
- 简单数字时钟🕒最好的WordPress常用插件下载博客插件模块
- Scoreboard UI 最好的WordPress常用插件下载 博客插件模块
- Android 线程死锁场景与优化
- 跟🤡杰哥一起学Flutter (七、项目实战-非UI部分🤷♀️)
- Jetpack Compose 实战之仿微信UI -实现聊天界面(五)
- 操作系统底层运行原理 —— 基于线程安全的消息机制
- 如何应对Android面试官->阻塞队列和线程池原理,手写自动收货系统核心实现
- Ele UI Color Scheme Restoration 最好的WordPress常用插件下载 博客插件模块
- Glassmorphic Admin UI 最好的WordPress常用插件下载 博客插件模块
- Embed Swagger UI 最好的WordPress常用插件下载 博客插件模块
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 9鸿蒙HarmonyOS实战-ArkUI组件(GridRow/GridCol)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版