深入浅出Android同步屏障机制

原文链接 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 导致主线程假死

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/69985.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6中导入import导出export

ES6使用 export 和 import 来导出、导入模块 用法 /** 导出 export *///分别导出 export let name 孙悟空; export function sum(a, b) {return a b; } } //先定义再导出 let age 18 export {age}/** 默认导出 export default */const a 默认导出; export default a;/**…

Java复习-18-抽象类

抽象类 之前的类继承,子类可以选择是否要覆写某一个方法,这个时候父类无法对子类做出强制性约定(强制你必须覆写某些方法)。这种时候不会使用类的继承,即在实际开发中很少会继承一个已经完善的类——可以直接使用的类…

手写apply方法

<script>/** 手写apply方法 * */Function.prototype.myApply function (context, args) {console.log(this, sss)//fnconst key Symbol()context[key] thiscontext[key](...args)delete context[key]return context[key]}const obj {name: zs,age: 18}function fn …

$ref属性的介绍与使用

在Vue.js中&#xff0c;$ref是一个特殊的属性&#xff0c;用于访问Vue组件中的DOM元素或子组件实例。它允许你直接访问组件内部的DOM元素或子组件&#xff0c;并且可以在需要时进行操作或修改。以下是有关$ref的详细介绍和示例演示&#xff0c;给大家做一个简单的介绍和概念区分…

STL- 函数对象

1 函数对象 1.1 函数对象概念 概念&#xff1a; 重载函数调用操作符的类&#xff0c;其对象常称为函数对象函数对象使用重载的()时&#xff0c;行为类似函数调用&#xff0c;也叫仿函数 本质&#xff1a; 函数对象(仿函数)是一个类&#xff0c;不是一个函数 1.2 函数对象…

Leetcode 1486.数组异或操作

给你两个整数&#xff0c;n 和 start 。 数组 nums 定义为&#xff1a;nums[i] start 2*i&#xff08;下标从 0 开始&#xff09;且 n nums.length 。 请返回 nums 中所有元素按位异或&#xff08;XOR&#xff09;后得到的结果。 示例 1&#xff1a; 输入&#xff1a;n 5, …

YOLOv5:对yolov5n模型进一步剪枝压缩

YOLOv5&#xff1a;对yolov5n模型进一步剪枝压缩 前言前提条件相关介绍具体步骤修改yolov5n.yaml配置文件单通道数据&#xff08;黑白图片&#xff09;修改models/yolo.py文件修改train.py文件 剪枝后模型大小 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;…

小程序中如何查看会员的积分和变更记录

​积分是会员卡的一个重要功能&#xff0c;可以用于激励会员消费和提升用户粘性。在小程序中&#xff0c;商家可以方便地查看会员卡的积分和变更记录&#xff0c;以便更好地了解会员的消费行为和积分变动情况。下面将介绍如何在小程序中查看会员卡的积分和变更记录。 1. 找到指…

算法训练day37|贪心算法 part06(LeetCode738.单调递增的数字)

文章目录 738.单调递增的数字思路分析代码实现 738.单调递增的数字 题目链接&#x1f525;&#x1f525; 给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的…

Nat. Communications Biology2022 | PepNN+: 用于识别多肽结合位点的深度关注模型

论文标题&#xff1a;PepNN: a deep attention model for the identification of peptide binding sites 论文链接&#xff1a;PepNN: a deep attention model for the identification of peptide binding sites | Communications Biology 代码地址&#xff1a;oabdin / PepN…

【管理运筹学】第 7 章 | 图与网络分析(1,图论背景以及基本概念、术语、矩阵表示)

文章目录 引言一、图与网络的基本知识1.1 图与网络的基本概念1.1.1 图的定义1.1.2 图中相关术语1.1.3 一些特殊图类1.1.4 图的运算 1.2 图的矩阵表示1.2.1 邻接矩阵1.2.2 可达矩阵1.2.3 关联矩阵1.2.4 权矩阵 写在最后 引言 按照正常进度应该学习动态规划了&#xff0c;但我想…

fatal error: -fuse-linker-plugin, but liblto_plugin.so not found 解决方法

参考文章&#xff1a;https://blog.csdn.net/tt_tantao/article/details/91646875 在工具链目录下找到 liblto_plugin.so.0.0.0 复制成一份 liblto_plugin.so 顺利解决

Android11 有线网和wifi优先级设置

一、优先级基本知识介绍 Android6.0之后系统中优先级设置都是根据Score分值来设置优先级&#xff0c;分值0-100&#xff0c;数值越高&#xff0c;越优先。 系统默认分值&#xff1a; SIM卡网络 50 wifi网络 60 有线网络 70手机网络设置都有自己的Factory设置类&#xff0c…

架构师 软件测试

架构师 软件测试 目录概述需求&#xff1a; 设计思路实现思路分析1.软件测试方法 软件测试工具 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for c…

C语言入门 Day_14 for循环

目录​​​​​​​ 1.for循环 2.循环执行顺序 3.易错点 4.思维导图 前言 我们定义了一个数组以后&#xff0c;要使用&#xff08;读取或者修改&#xff09;数组元素的话&#xff0c;可以一个一个的读取&#xff0c;就前两课学的那样&#xff0c;代码类似这个结构。 int …

再思考设计模式

学习技巧&#xff0c;化整为零&#xff0c;量化记忆&#xff0c;逐个击破 1、设计模式的目标&#xff0c;6个 可读性便于他人阅读和理解可重用性相同代码无需多次编写可扩展性添加新的功能比较容易可靠性添加新功能后不影响原有功能可维护性便于他人开发维护高内聚、低耦合功…

基于SpringBoot的Web开发案例过程讲解-项目准备

基于SpringBoot的Web开发案例过程笔记-项目准备 1&#xff09;环境搭建【1】准备数据库表【2】创建Springboot项目并引入相关依赖【3】配置application.properties文件【4】创建相关的包和类 2) 三层架构工作流程3&#xff09;开发规范-Restful4&#xff09;相关的注解5)项目开…

Laravel 完整开源项目大全

原型项目 Laravel 5 Boilerplate —— 基于当前Laravel最新版本&#xff08;Laravel 6.0&#xff09;并集成Boilerplate的项目Laravel 5 Angular Material Starter —— 这是一个 Laravel 和 AngularJS 的原型项目&#xff08;最高支持版本&#xff1a;5.3&#xff0c;长期未更…

Qt Creato配置PCL库

Qt Creator中使用PCL库_业务不精er的博客-CSDN博客 Qt6.1.0中配置pcl1.11.1_qt6导入pcl库_朽一的博客-CSDN博客 VS2017 中配置QTPCL显示点云或3D图形_pcl显示3d图tiff_桂林巡山的博客-CSDN博客 Windows10下QTVTKPCL环境配置&#xff08;一次成功&#xff09;_qt pcl_v俊逸的…

MySQL中分区与分表的区别

MySQL中分区与分表的区别 一、分区与分表的区别 分区和分表是在处理大规模数据时的两种技术手段&#xff0c;尽管它们的目标都是提升系统的性能和数据管理的效率&#xff0c;但它们的实现方式和应用场景略有不同。 1. 分区 分区是将一个大表分割为多个更小的子表&#xff0c…