深入浅出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 导致主线程假死
原创不易,打赏,点赞,在看,收藏,分享 总要有一个吧
相关文章:

深入浅出Android同步屏障机制
原文链接 Android Sync Barrier机制 诡异的假死问题 前段时间,项目上遇到了一个假死问题,随机出现,无固定复现规律,大量频繁随机操作后,便会出现假死,整个应用无法操作,不会响应事件ÿ…...

工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计
鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展,企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性,公司对内部工程管…...
Python 专栏目录索引
文章目录 Python 环境搭建Python 语法 变量、print、注释和运算符Python 的基本结构Python 中的文件和文件夹操作Python 中常用库Python 常见问题及解决方案Python 应用实例 Python 环境搭建 vscode搭建Python环境 Python 语法 变量、print、注释和运算符 python语法 变量、…...
SSM学习
技术架构 crm的技术架构: 视图层(view):展示数据,跟用户交互。 html, css,js,jquery,bootstrap(ext / easyUI),jsp控制层(Controller):控制业客处理流程(接收请求,接收参数,封装参数;根据不同的请求调用业务 (servlet, ) springMVC ( , webwork,strutsl,struts2)业…...
.net项目部署Docker
1、项目生成的bin目录下创建Dockerfile文件 #运行环境描述,此处是用的Net5构建镜像 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build #复制文件到 docker容器中的app文件夹中 COPY . /app #设置工作目录为 app 文件夹,要和上面一致哦 WORKDIR /app #设…...
Ubuntu无法加载exfat的USB存储设备
当接入设备USB存储设备提示: 不能挂在63GB 卷 Error mounting /dev/sdb1 at /media/ubuntu/83C9-26F4: Command-line mount -t "exfat" -o "uhelperudisks2,nodev,nosuid,uid1000,gid1000,iocharsetutf8,namecase0,errorsremount-ro,umask0077"…...
【计算机网络】网络编程接口 Socket API 解读(2)
Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。 本文讲述的 socket 内容源自 Linux 发行版 centos 9 上的 man 工具,和其他平台(比如 os-x …...
【黄啊码】PHP如何防止重复提交
防抖(Debounce)是一种防止重复提交的策略,它通过延迟一定时间来合并连续的操作,以确保只执行一次。 以下是几种防抖的实现方法以及对应的代码示例: 1. 前端 JavaScript 实现: 在前端使用 JavaScript 实现…...

2594. 修车的最少时间
文章目录 Tag题目来源题目解读解题思路方法一:二分枚举答案 写在最后 Tag 【二分枚举答案】【数组】 题目来源 2594. 修车的最少时间 题目解读 给你一个表示机械工能力的数组 ranks,ranks[i] 表示第 i 位机械工可以在 r a n k s [ i ] ∗ n 2 ranks[…...

vue 使用qrcode生成二维码并可下载保存
安装qrcode npm install qrcode --save代码 <template><div style"display: flex; flex-direction: column; align-items: center; justify-content center;"><div>查看溯源码,<a id"saveLink" style"text-decorati…...
网络融合的发展思路
虽然移动和固定网的融合代表了下一代网络的发展方向,但是目前移动和固定网的 发展还是独立的,有着各自的演进方式,要实现两个网络的完全融合是一个长期 的、逐步发展的过程。 网络融合的体系结构首先应坚持网络分层和功能分离的原则&#…...

报考浙江工业大学MBA项目如何选择合适的辅导班?
浙江工业大学MBA项目每年有数百人报考,在浙江省内除了浙大以外算是人数比较多的一个项目。2023级的招生中第一志愿也通过复试刷掉了百来人,在省内其实作为第一志愿报考的风险在逐渐增大,考生们如果坚持报考,则在针对联考初试的备考…...
算法训练第五十八天
总结:今日事单调栈的开端,还是挺巧妙的。 496. 下一个更大元素 I - 力扣(LeetCode) 代码: class Solution { public:vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& …...
如何快速生成一个H5滑动的卡片(单页和分页都有)
单页 <ul class"combo"><li v-for"(item, index) in arr" :key"index"><div class"combo-name">{{ item.A }}</div><div class"combo-price">{{ item.B }}</div><div class"co…...

嵌入式开发笔试面试
C语言部分: 1.gcc的四步编译过程 1.预处理 展开头文件,删除注释、空行等无用内容,替换宏定义。 gcc -E hello.c -o hello.i 2.编译 检查语法错误,如果有错则报错,没有错误则生成汇编文件。 gcc -S hello.i -o h…...

2023国赛数学建模B题思路分析 - 多波束测线问题
# 1 赛题 B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播, 在不同界面上产生反射, 利用这一原理,从测量船换能器垂直向海底发射声波信 号,并记录从声波发射到…...

thinkphp6 入门(5)-- 模型是什么 怎么用
一、模型 MVC架构 之前开发一个功能,后端为在控制器(C)中写 php SQL,前端为在页面(V)中写html css js,这就形成了 VC 架构。 但是发现,相同的数据逻辑(SQL…...

Hadoop HDFS 高阶优化方案
目录 一、短路本地读取:Short Circuit Local Reads 1.1 背景 1.2 老版本的设计实现 1.3 安全性改进版设计实现 1.4 短路本地读取配置 1.4.1 libhadoop.so 1.4.2 hdfs-site.xml 1.4.3 查看 Datanode 日志 二、HDFS Block 负载平衡器:Balan…...

通俗易懂讲解大模型:Tokenizer
Tokenizer Tokenizer 是 NLP pipeline 的核心组件之一。Tokenizer 的目标是:将文本转换为模型可以处理的数据。模型只能处理数字,因此 Tokenizer 需要将文本输入转换为数字输入。 通常而言有三种类型的 Tokenizer :Word-based Tokenizer、Cha…...

nested exception is java.io.FileNotFoundException
完整的错误信息: [main] ERROR o.s.boot.SpringApplication - Application run failed org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.heima.article.ArticleApplication]; nested exception is java…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...