原文链接 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机制,就是队列,也就是说合乎队列的特点,先进先出(FIFO,First-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 event,keyevent 以及与用户输出相干的音讯被设置为了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 导致主线程假死

原创不易,打赏点赞在看珍藏分享 总要有一个吧