深入浅出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.rhkb.cn/news/128659.html

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

相关文章

【Linux】Systemd 中的单元(Unit)和单元文件(Unit File)怎么理解?

单元(Unit)单元文件(Unit File)感谢 💖 关于systemd是什么,http://t.csdn.cn/pMkG7这篇文章里有详细说明。 这篇文件我们一起来看看Systemd 中的单元(Unit)和单元文件(Un…

vue使用jsencrypt实现rsa前端加密

实现 RSA 加密 介绍 vue 完成 rsa 加密传输,jsencrypt 实现参数的前端加密 1 安装 jsencrypt npm install jsencrypt2 编写 jsencrypt.js 在 utils 文件夹中新建 jsencrypt.js 文件,内容如下:注意点:一般公钥都是后端生成好的&a…

excl在建模语言中的运用

目录 1.表格的定位 2.数学函数 3.自动填充功能 4.数据透视表的应用 5.切片器 6. Date(),time(),now(),today() 7.文本转日期 8.分裂 9.sumif函数 10.数字转换为文本的方法 11.SUMIFS()函数:多个条件筛选 12.宏 13.提取多个表中,…

大秒杀系统设计

参考链接:http://www.taodudu.cc/news/show-5770725.html?actiononClick 1. 一些数据 大家还记得2013年的小米秒杀吗?三款小米手机各11万台开卖,走的都是大秒系统,3分钟后成为双十一第一家也是最快破亿的旗舰店。 经过日志统计…

appium+jenkins实例构建

自动化测试平台 Jenkins简介 是一个开源软件项目,是基于java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。 前面我们已经开完测试脚本,也使用bat 批处…

文件上传之图片码混淆绕过(upload的16,17关)

目录 1.upload16关 1.上传gif loadup17关(文件内容检查,图片二次渲染) 1.上传gif(同上面步骤相同) 2.条件竞争 1.upload16关 1.上传gif imagecreatefromxxxx函数把图片内容打散,,但是不会…

MySQL用户管理

文章目录 MySQL用户管理1. 用户1.1 用户信息1.2 创建用户1.3 删除用户1.4 修改用户密码 2. 数据库的权限2.1 给用户授权2.2 回收权限2.2 回收权限 MySQL用户管理 如果我们只能使用root用户,这样存在安全隐患。这时,就需要使用MySQL的用户管理。 1. 用户…

flink的几种常见的执行模式

背景 在运行flink时,我们经常会有几种不同的执行模式,比如在IDE中启动时,通过提交到YARN上,还有通过Kebernates启动时,本文就来记录一下这几种模式 flink的几种执行模式 flink嵌入式模式: 这是一种我们在…

自考本科,毕业八年,2023浙大MPA提面优秀分享

去年十月中旬,我参加了浙江大学MPA提前批面试。结果出乎意料地,我竟然获得了A资格。对此,我自己也感到难以置信。事实上,我只是抱着试一试的心态递交了申请材料。因为通过我对前几年浙大自划线的情况来看,对于浙江大学…

用postman 推送消息到GCP的pubsub

创建1个Topic 和 2个 subscription 我们可以用terraform 去创建1个topic 和 2个subscriptions # topic resource "google_pubsub_topic" "topic_a" {name "TopicA"project var.project_id }# subscriptions resource "google_pubsub_s…

SpringMVC的常用注解,参数传递以及页面跳转的使用

目录 slf4j 常用注解 RequestMapping RequestParam RequestBody PathVariable 参数传递 首先在pom.xml配置文件中导入SLF4J的依赖 基础类型String 复杂类型 RequestParam PathVariable RequestBody 增删改查 返回值 void返回值 String返回值 modelString …

【算法】分治法的应用——快速排序

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

关于黑马hive课程案例FineBI中文乱码的解决

文章目录 问题描述情况一的解决情况二的解决 ETL数据清洗知识社交案例参考代码结果展示 问题描述 情况1&#xff1a;FineBI导入表名中文乱码&#xff0c;字段内容正常情况2&#xff1a;FineBI导入表字段中文乱码&#xff0c;表名内容正常 情况一的解决 使用navcat等工具连接…

Docker如何安装seafile

SQLite 方式 要在 Docker 中安装 Seafile&#xff0c;您可以按照以下步骤进行操作&#xff1a; 安装 Docker&#xff1a;确保您的系统上已经安装了 Docker。您可以根据您的操作系统类型&#xff0c;在官方网站上找到适合您系统的 Docker 版本并进行安装。 下载 Seafile 镜像&…

第16章_瑞萨MCU零基础入门系列教程之CAN 协议

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

Linux CentOS7命令及命令行

Linux CentOS7中命令及命令行是非常重要的概念。对大多数初学者来说是既熟悉又了解甚少。本文初步讨论这方面的内容&#xff0c;与同行者交流。 一、命令 命令又称为指令&#xff0c;&#xff08;英语命令 command&#xff0c;可用简写cmd表示&#xff09;&#xff0c;在终端…

Spring Boot集成JasperReport生成文档

由于工作需要&#xff0c;要实现后端根据模板动态填充数据生成PDF文档&#xff0c;通过技术选型&#xff0c;使用Ireport5.6来设计模板&#xff0c;结合JasperReports5.6工具库来调用渲染生成PDF文档。 一、使用Ireport designer 5.6设计模板 ireport的使用由于时间关系不便多…

ISYSTEM调试实践12-软件运行时间的优化

实际工程的运行要比上篇文章提到的例程复杂的多 ISYSTEM调试实践11-Profiler Timeline和软件运行时间分析 由于复杂的应用层模型和底层任务&#xff0c;假定应用层模型的运行周期是10ms&#xff0c;任务函数的执行时间往往超过1ms&#xff0c;这时候就必须要考虑函数执行本身的…

sqli --【1--10】

Less-1&#xff08;联合查询&#xff09; 1.查看是否有回显 2.查看是否有报错 3.使用联合查询&#xff08;字符注入&#xff09; 3.1判断其列数 3.2 判断显示位置 3.3敏感信息查询 Less-2&#xff08;联合查询&#xff09; 1.查看是否有回显 2.查看是否有报错 3.使用…

idea启动缓慢解决办法

idea启动缓慢解决办法 文章目录 idea启动缓慢解决办法前言一、修改内存大小二、虚拟机运行大小三、插件禁用1、安卓相关2、构建工具3、Code Coverage 代码覆盖率4、数据库5、部署工具6、html和xml7、ide settings8、JavaScript框架和工具9、jvm框架10、Keymap快捷键映射11、kot…