成都信用建设网站和外国人做古玩生意的网站
news/
2025/9/26 4:22:37/
文章来源:
成都信用建设网站,和外国人做古玩生意的网站,香河建设局网站,海口网站建设搜q.479185700原文链接 Android Sync Barrier机制
诡异的假死问题
前段时间#xff0c;项目上遇到了一个假死问题#xff0c;随机出现#xff0c;无固定复现规律#xff0c;大量频繁随机操作后#xff0c;便会出现假死#xff0c;整个应用无法操作#xff0c;不会响应事件#xff…原文链接 Android Sync Barrier机制
诡异的假死问题
前段时间项目上遇到了一个假死问题随机出现无固定复现规律大量频繁随机操作后便会出现假死整个应用无法操作不会响应事件会发生各种奇怪的ANR且trace不固定。非常之诡异。
经过大量的复现研究和分析 以及大神的指点后发现与同步屏障Sync Barrier有关系于是发现有必要研究一下这个东西。 什么是Sync Barrier机制
这是安卓线程消息队列里面的一个新增加的东西这么说还是太抽象我们从头说起这件事情
安卓的消息队列机制
消息队列或者叫做Event Loop通常在任何一个GUI应用程序里面都会有的应用大部分时间处于Idle状态当有事件发生时比如用户点了一个button然后开始响应此事件。安卓也是一个GUI应用程序绝大多数都是带有GUI的应用程序那么安卓 里面是如何实现这个EventLoop的呢它是用Looper和MessageQueue以及Handler以一种消息队列的方式来实现loop。
有一定经验的同学对这些东西肯定不陌生因为它们在实际的开发过程中相当常见比如说对于UI的操作只能放在主线程里面那么当工作线程想要更新UI时就需要用Handler发一个消息或者post一个Runnable。或者当你想延后一段时间执行某种操作就可以用postDelayed。这些都是非常常规的操作了。对于工作线程如果想启用消息队列就用Looper#prepare就可以了当然了要记得quit。
内部原理上面也不是很复杂就是Looper会给线程绑定一个消息队列即是MessageQueue这是一个无限循环的队列不断的轮询队列当有新的消息时就去处理否则就等待。主线程安卓框架层在创建应用进程的时候就会给主线程默认创建好MessageQueue所以就可以向其发消息sendMessage或者postDelayed它们本质上都是一样的都是向MessageQueue中入队一个消息稍后它便会得到处理。 同步消息与异步消息
这个MessageQueue机制就是队列也就是说符合队列的特点先进先出FIFOFirst-In First Out就是说你先post的消息肯定是先被处理后post的后处理即使有delay时候也是看谁先到谁先到谁先被处理。因此这里面的消息全是同步也就是说所有消息都是顺序处理这就是同步消息。
异步消息也就是说某个消息想被最高优先级处理无视发送消息的时机比如说队列里面有8个消息如何想让某个消息最先被处理这时队列就变成了优先队列有优先级的队列。那么具有高优先级的消息也是异步消息Asynchronous Message。即使是最后加入队列的但因为是异步消息它会被先处理并不是FIFO此可理解 为异步。
Sync Barrier用以实现优先队列
说了这么多Sync Barrier就是安卓 内部用以实现优先级队列的一种方式。
当队列中出现Sync barrier具体实现上就是Message#target为null时就会忽略所有同步消息寻找异步消息isAsynchrouns为true的消息然后优先处理它。
需要注意的是把消息标记为异步以及向消息队列中发送Sync barrier这些API全部都是hide的也就是说app中是无法使用的通过反射也许能调用成功但风险也较大后续会被谷歌限制调用。换言之这东西只能在Frameworks层内部自己使用。
为什么要有Sync Barrier
说了这么多其实本质上这东西就是一个优先队列给要处理的消息加一个优先级机制那这有什么实际用途呢
消息队列这东西是在安卓一诞生就有了的东西大部分时候它也没有什么问题。但有一个事情就是安卓操作系统的UI流畅度远不及水果平台iOS原因就是在于水果平台的UI渲染是整个系统中最高优先执行。
有同学会说安卓里面也是这样啊你想UI都只能在主线程里面操作因此主线程也叫UI线程。只能在主线程中操作UI就能保证UI渲染是最高优先级吗当然不是了。因为整个应用程序的默认线程就是主线程换句话说如果你不明显的去做线程切换或者启用工作线程那么所有事情都发生在主线程里面当然 也包括了UI渲染因此UI的渲染与你在主线程时面post一个消息的优先级是一样的。
如何让UI渲染在主线程中以最高优先级运行于是就有了Sync barrier机制这东西就是为了让消息队列有优先级并且没有开放给app使用。可以去看一下ViewRootImpl这货是专门负责ViewTree渲染的也即可以理解为负责UI渲染的的几个perform它都是异步消息也即会开启Sync barrier它发送的消息将会是最高优先级的会被优先处理。
主要在哪里用Sync barrier
前面提到了Sync barrier这玩意儿并不是给app开发同学用的很多相关的接口并没有开放出来这是为了提高UI渲染而设计的东西。因此这东西主要是用在了UI渲染过程中。
仔细查看ViewRootImpl的源码可以发现每次渲染View tree之前都会先给主线程插入一个Sync barrier以挡住同步消息以保证渲染被主线程优先执行到。 UnsupportedAppUsage(maxTargetSdk Build.VERSION_CODES.R, trackingBug 170729553)void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled true;mTraversalBarrier mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}void unscheduleTraversals() {if (mTraversalScheduled) {mTraversalScheduled false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);}}void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);performTraversals();}}这里的逻辑略复杂一些View tree本身的处理过程也即三大步measure, layout和draw也就是performTraversal本身并没有异步消息它是在准备渲染的时候放一个sync barrier而在具体处理每一帧前就移除了sync barrier这里为何要这样还没有完全想清楚。通过搜索ViewRootImpl可以发现只有input eventkeyevent 以及与用户输入相关的消息被设置为了asynchronous也就是说用户事件响应被提高了优先级而view tree的渲染即UI的每一帧其实并没有被提升优先级。因为UI刷的每一帧是以固定频率刷新的Choreographer 从硬件得到vsync脉冲信号然后回调给ViewRootImpl让其渲染每一帧也即是performTraversal。
Sync Barrier会引发什么问题
说实话这套机制实现的并不怎么优雅因为毕竟它并不是在最初的设计之初就考虑到的东西它的整体运行机制并不完善非常依赖于调用者的使用所以它的相关API并未有开放出来。
它有三步先发一个Sync barrier然后发送异步消息然后再移除Sync barrier。
只有UI渲染ViewTree的相关操作才需要这样做大部分其他的消息都是同步的并不需要这样搞。当有Sync barrier时消息队列在处理消息的时候会忽略掉所有的同步消息也即是常规消息优先处理异步消息直到Sync barrier移除也是需要手动移除的。Sync barrier需要手动移除是最坑的。
因此假如要处理的异步特别多或者逻辑出错Sync barrier没有被移除那就悲剧 了就会导致消息队列中的大量常规消息无法得到处理队列就会停止工作应用会出现随机的ANR以及假死。
如何调试
很不幸Sync barrier导致的问题很难调试甚至很难被发现通常都是ANR或者说卡死问题。
那么首先可以按照ANR和卡死的常规分析方式去分析假如都未发现明显的问题时比如没有明显的耗时的操作也没有死锁也没有被硬件和IO阻塞也没有进入死循环。
这些常规的分析都没有发现问题。这时就可以考虑是不是Sync barrier在搞鬼。特别当涉及一些诡异的UI状态时比如某个View只显示 了一半比如某一个View没有显示 完全比如只有背景没有前景等等当排除了其他常规问题时就很可能是Sync barrier有异常导致的。
另外如果有能力修改Frameworks的话可以给MessageQueue增加dump信息把队列中的所有消息都打印出来以及把Sycn barrier也都打印出来这样能够比较清楚看到队列内部的情况自然也能够发现异常的Sync barrier。
如何避免Sync Barrier搞鬼
前面提到过这套东西都是Frameworks层内部的机制并没有开放给app使用而Frameworks内部的逻辑一般来说还是相当健壮的绝大多数时候并不会出问题。当然了各个厂商内部搞的各种所谓优化倒是有可能会引发问题。
在实际开发过程中引发Sync barrier的最多场景就是自定义View。对于自定义View是能够在非主线程调用其invalidate的当有大量的非主线程调用invalidate时就有可能恰好与主线程的渲染发生交互具体case非常corner要刚巧非主线程在postInvalide然后主线程也刚巧在发送异步消息就可能使得Sync barrier没有被移除从而导致问题。
这就需要我们在编码阶段做好封装对于自定义View的刷新触发逻辑做好封装做一下线程切换以保证是在主线程里面执行invalidate。因为暴露出去的接口是没有办法控制的你没有办法让所有调用者都在主线程里面调用你的接口。
参考资料
Handler sync barrier同步屏障Android 同步屏障机制(Sync Barrier)同步屏障阻塞唤醒和我一起重读 Handler 源码同步屏障与异步消息从入门到放弃面试官如何提高Message的优先级今日头条 ANR 优化实践系列 - Barrier 导致主线程假死
原创不易打赏点赞在看收藏分享 总要有一个吧
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/917852.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!