耐克运动鞋网站建设规划书框架,宿迁网站优化,wordpress 小视频主题,深圳房产 网站建设前言
在项目中经常遇到了手机假死问题#xff0c;无规律的偶现问题#xff0c;大量频繁随机操作后#xff0c;便会出现假死#xff0c;整个应用无法操作#xff0c;不会响应事件#xff0c;会发生各种奇怪的ANR#xff0c;且trace不固定。而SyncBarrier是其中的罪魁祸首…前言
在项目中经常遇到了手机假死问题无规律的偶现问题大量频繁随机操作后便会出现假死整个应用无法操作不会响应事件会发生各种奇怪的ANR且trace不固定。而SyncBarrier是其中的罪魁祸首之一
SyncBarrier的介绍
SyncBarrier大家又称它为同步屏障这是安卓线程消息队列里面的一个新增加的东西它是一种Handler中的同步屏障机制。简单可以理解安卓在Hanlder的处理上增加了优先级优先级最高的就是SyncBarrier。
1、消息分类
Handler中的Message可以分为两类同步消息体优先级高、异步消息体优先级低。可以通过Message.java的isAsynchronous()知道是否为异步消息体
public boolean isAsynchronous() {return (flags FLAG_ASYNCHRONOUS) ! 0;
}2、SyncBarrier是什么
SyncBarrier可以通过MessageQueue.postSyncBarrier()发送一个同步消息体该消息唯一的区别点在于Message没有target
private int postSyncBarrier(long when) {// Enqueue a new sync barrier token.// We dont need to wake the queue because the purpose of a barrier is to stall it.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;if (when ! 0) {while (p ! null p.when when) {prev p;p p.next;}}if (prev ! null) { // invariant: p prev.nextmsg.next p;prev.next msg;} else {msg.next p;mMessages msg;}return token;}
}跟以往相比以往的Handler发送消息最终都会调用enqueueMessage函数
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target this;//...return queue.enqueueMessage(msg, uptimeMillis);
}可以知道enqueueMessage会设置了msg.target this;从代码层面上同步屏障就是一个target字段为空的Message
3、SyncBarrier的作用
当队列中出现SyncBarrier具体实现上就是Message#target为null时就会忽略所有异步消息体寻找同步消息体然后优先处理它这些API全部都是hide的也就是说app中是无法使用的谷歌设计初衷也是系统开发人员自己用的消息队列这东西是在安卓一诞生就有了的东西大部分时候它也没有什么问题。但有一个事情就是安卓操作系统的UI流畅度远不及水果平台iOS原因就是在于水果平台的UI渲染是整个系统中最高优先执行。于是就有了SyncBarrier机制这东西就是为了让消息队列有优先级它发送的消息将会是最高优先级的会被优先处理这样来达到UI优先渲染达到提高渲染速度的目的
Message next() {for (;;) {//......synchronized (this) {final long now SystemClock.uptimeMillis();Message prevMsg null;Message msg mMessages;// 1、碰到同步屏障if (msg ! null msg.target null) {// 2、循环遍历消息链表在表头插入同步屏障do {prevMsg msg;msg msg.next;} while (msg ! null !msg.isAsynchronous());}if (msg ! null) {if (now msg.when) {//...} else {// Got a message.mBlocked false;if (prevMsg ! null) {prevMsg.next msg.next;} else {mMessages msg.next;}msg.next null;if (DEBUG) Log.v(TAG, Returning message: msg);msg.markInUse();// 3、返回当前的消息return msg;}} else {nextPollTimeoutMillis -1;}}}
}可以看到当设置了同步屏障之后next()将会忽略所有的异步消息体返回同步屏障消息。
4、SyncBarrier的发送
通常我们使用Handler发消息时这些消息都是同步消息体如果我们想发送异步消息体那么在创建Handler时使用以下构造函数中的其中一种(async传true)通过该Handler发送的所有消息都会变成异步消息体
public Handler() {this(null, false);
}public Handler(boolean async) {this(null, async);
}public Handler(NonNull Looper looper) {this(looper, null, false);
}5、SyncBarrier的应用
前面说到SyncBarrier并不是给app开发同学用的很多相关的接口并没有开放出来这是为了提高UI渲染而设计的东西。因此这东西主要是用在了UI渲染过程中。仔细查看ViewRootImpl的源码可以发现每次渲染View之前都会先给主线程插入SyncBarrier以挡住异步消息体保证渲染被主线程优先执行
UnsupportedAppUsage(maxTargetSdk Build.VERSION_CODES.R, trackingBug 170729553)
void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled true;// 1、设置同步障碍确保mTraversalRunnable优先被执行mTraversalBarrier mHandler.getLooper().getQueue().postSyncBarrier();// 2、内部通过Handler发送了一个异步消息mTraversalRunnablemChoreographer.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);}
}// 3、mTraversalRunnable最终执行到这里
void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);// 4、执行measure、layout、drawperformTraversals();}
}6、SyncBarrier的泄露
Barrier消息像一道栅栏将消息队列里的普通消息先拦住多数情况下是正常但一旦异常则很容易发生ANR且ANR的trace都是莫名其妙的但是也有些情况是Barrier引起的trace就停在nativePollOnce()当然这里指的是小部分情况而非所有的nativePollOnce()都是SyncBarrier引起的具体情况具体分析
正常情况渲染刷新类先优先执行等执行完以后撤掉栅栏普通消息包括会导致ANR的消息得以继续执行异常情况Barrier存在泄漏导致无法释放栅栏普通消息卡住不动UI假死如果期间有Server或者Provider等消息超时就会引发ANR
一旦发生Barrier的泄露在取消息的时候优先进入同步屏障的逻辑主线程会过滤掉所有非异步消息!msg.isAsynchronous()一直在死循环中出不来只有移除当前的同步屏障后才得以解开
if (msg ! null msg.target null) {do {prevMsg msg;msg msg.next;} while (msg ! null !msg.isAsynchronous());
}如下图正常情况下是执行145236而异常情况是Barrier在此没有被移除导致236都无法执行 7、SyncBarrier的问题
SyncBarrier产生的问题往往是异步刷新导致的比如子线程触发invalidate()UI频繁更新自定义View写法不对surfaceview异步刷新等等 从上图可以看出如果子线程同时多次进入mTraversalBarrier mHandler.getLooper().getQueue().postSyncBarrier();那么就会发送多个Barrier但是在移除掉的时候只移除当前成员变量mTraversalBarrier一个Barrier多余的就会导致泄露 8、SyncBarrier的模拟问题
1.创建子线程频繁刷新UI的自定义View
class ThreadView JvmOverloads constructor(context: Context, attrs: AttributeSet? null, defStyleAttr: Int 0
) : View(context, attrs, defStyleAttr) {var i 1Fvar paint Paint()fun start() {object : Thread(funny1) {override fun run() {super.run()while (true) {invalidate()}}}.start()}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {setMeasuredDimension(getDefaultSize(suggestedMinimumWidth, widthMeasureSpec),getDefaultSize(suggestedMinimumHeight, heightMeasureSpec))}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)i 100fif (i 2000f) {i 0f}paint.setAntiAlias(false)paint.setColor(Color.BLACK)paint.setStrokeWidth(3f)canvas?.drawCircle(i, i, 90f, paint)}
}2.主线程频繁随机更新背景色
var runningThread object : Runnable {val random Random()override fun run() {val r: Int random.nextInt(256)val g: Int random.nextInt(256)val b: Int random.nextInt(256)rootView?.setBackgroundColor(Color.rgb(r, g, b))mHandler.postDelayed(this, 100)}
}3.通过设置Looper取消息的接口Looper.getMainLooper().setMessageLogging(CustomPrinter())输出我们想要的日志
public void println(String reason) {if (reason.charAt(0) ) {Message message getMessage();if (message.getTarget() null) {Log.e(Hensen, [token message.arg1 ] [target message.getTarget() ] [when message.getWhen() ] [next getNext(message) ]);}}
}4.运行效果当背景色卡住的时候此时主线程明显被阻塞也就是说Barrier泄漏的现场 5.在卡住的时候通过日志也可以看得出来当前looper消息一直卡在[token41208][targetnull]的消息中该消息就是Barrier
2023-03-02 17:05:47.005 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s563ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]
2023-03-02 17:05:47.011 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s569ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]
2023-03-02 17:05:47.022 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s580ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]
2023-03-02 17:05:47.031 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s589ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]
2023-03-02 17:05:47.038 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s597ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]
2023-03-02 17:05:47.045 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s603ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]9、SyncBarrier问题的解决
我们通过反射MainLooper的mMessage如果当前的Message的target为null并且这个Message的when时间已经存在很久的话这个时候我们会怀疑产生了SyncBarrier的泄漏。但此时还不能完全确定因为如果当时因为其他原因导致主线程卡死也可能会导致这种现象。然后我们再起一个handler往MainLooper发送一个同步消息和一个异步消息并且发两次doublecheck。如果异步消息被处理了但是同步消息一直无法被处理这时候就说明产生了SyncBarrier的泄漏。
1.通过Timer启动一个1s的轮询任务
private fun startCheckBarrier() {val checkBarrierTimer Timer(syncBarrier)checkBarrierTimer.schedule(AutoCheckerTask(), 20000L, 1000L)
}2.每次轮询都会检查下当前的消息队列中是否有超过3s且targetnull的消息这里作为演示就直接移除了
class AutoCheckerTask : TimerTask() {RequiresApi(api 23)override fun run() {Log.e(KKK, detectSyncBarrierMessage)detectSyncBarrierMessage()}RequiresApi(Build.VERSION_CODES.M)fun detectSyncBarrierMessage() {try {val mainQueue Looper.getMainLooper().queueval field mainQueue.javaClass.getDeclaredField(mMessages)field.isAccessible trueval mMessage field[mainQueue] as Messageif (mMessage ! null) {val when SystemClock.uptimeMillis() - mMessage.getWhen()if (when 3000L mMessage.target null) {val token mMessage.arg1this.removeSyncBarrier(token)}}} catch (var7: Exception) {Log.e(SyncBarrierMonitor, var7.toString())}}RequiresApi(api 23)fun removeSyncBarrier(token: Int) {try {val mainQueue Looper.getMainLooper().queueval method mainQueue.javaClass.getDeclaredMethod(removeSyncBarrier, Integer.TYPE)method.isAccessible truemethod.invoke(mainQueue, token)Log.e(KKK, detectSyncBarrierMessage [token token ])} catch (var4: java.lang.Exception) {Log.e(SyncBarrierMonitor, var4.toString())}}
}3.通过日志可以看出移除了当前Barrier之后主线程也恢复了运行背景色开始又闪烁起来了
2023-03-02 17:05:47.038 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s597ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]
2023-03-02 17:05:47.045 8502-8502/com.example.syncbarriermonitor E/Hensen: [token41208][targetnull] [when5672519349][next{ when-6s603ms callbackcom.example.syncbarriermonitor.MainActivity$runningThread$1 targetandroid.os.Handler }]
2023-03-02 17:05:47.054 8502-8538/com.example.syncbarriermonitor E/KKK: detectSyncBarrierMessage
2023-03-02 17:05:47.054 8502-8538/com.example.syncbarriermonitor E/KKK: detectSyncBarrierMessage [token41208]参考资料
Handler之同步屏障机制(sync barrier)Android Sync Barrier机制让 nativePollOnce 不再排名第一 | 钉钉 ANR 治理最佳实践今日头条 ANR 优化实践系列 - Barrier 导致主线程假死