惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V2EX - 技术
V2EX - 技术
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
S
Securelist
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
量子位
博客园 - Franky
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
T
Troy Hunt's Blog
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
小众软件
小众软件
P
Palo Alto Networks Blog
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
WordPress大学
WordPress大学
L
Lohrmann on Cybersecurity
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
P
Privacy International News Feed
Last Week in AI
Last Week in AI
Microsoft Security Blog
Microsoft Security Blog
T
Tailwind CSS Blog
博客园_首页
云风的 BLOG
云风的 BLOG
V
Vulnerabilities – Threatpost
D
DataBreaches.Net
Recent Announcements
Recent Announcements
酷 壳 – CoolShell
酷 壳 – CoolShell
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
罗磊的独立博客
Engineering at Meta
Engineering at Meta
Forbes - Security
Forbes - Security
T
Tenable Blog

Android Performance

SmartPerfetto 架构文章 Q&A:8 个深度技术问答 从 Trace 到洞察:SmartPerfetto AI Agent 的 Harness Engineering 实战 OpenClaw 常见问题解答:Token 消耗、能干什么、本地模型、隐私安全、使用体验 我把 OpenClaw 跑在本地三周后,发现它根本不是聊天机器人 Android Perfetto 系列 10 - Binder 调度与锁竞争 Android Perfetto 系列 9 - CPU 信息解读 Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析 Android Perfetto 系列 7 - MainThread 和 RenderThread 解读 Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战 Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程 Android Perfetto 系列 4:使用命令行在本地打开超大 Trace Android ANR 系列 3 :ANR 案例分享 Android ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android ANR 系列 1 :理解 Android ANR 设计思想 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列目录 2023 年的方方面面 关于 The Android Performance 知识星球介绍 The Performance Design Of OS OS 设计之性能设计 当 App 有了系统权限,真的可以为所欲为? The Performance 星球茶话会 - 第一期 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Android 性能优化的术、道、器 Techniques, Philosophy, and Tools for Android Performance Optimization 回顾 2021 一本讲 Android 流畅性的书,应该有什么内容? Android 系统开发系列(1):Android 12 源代码下载、编译和刷机 Android Systrace 响应速度实战 3 :响应速度延伸知识 Android Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Android Systrace 响应速度实战 1 :了解响应速度原理 Android Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Android Systrace 流畅性实战 2 :案例分析 - MIUI 桌面滑动卡顿分析 Android Systrace 流畅性实战 1 :了解卡顿原理 华为手机刷微博体验更好?技术角度的一些分析和思考 Android App 链式唤醒分析 Android Systrace 基础知识 - SurfaceFlinger 解读 Android 开发者学习路线(2020 版本) 我的 2020 年读书单 Android Systrace 基础知识 - CPU Info 解读 Android Systrace 基础知识 - Triple Buffer 解读 Android Systrace 基础知识 - Binder 和锁竞争解读 「置顶」博客文章目录 Android Systrace 基础知识 - Vsync 解读 Android App 启动优化全记录 Android Systrace 基础知识 - MainThread 和 RenderThread 解读 Android Systrace 基础知识 - Input 解读 Android 中的“后台无效动画“行为分析 Android 基于 Choreographer 的渲染机制详解 Android 中的卡顿丢帧原因概述 - 低内存篇 Android 桌面被杀问题分析案例 Android 中的卡顿丢帧原因概述 - 应用篇 Android 中的卡顿丢帧原因概述 - 系统篇 Android 中的卡顿丢帧原因概述 - 方法论 Android 中的 Activity Launch Mode 详解 Android 中的 Hardware Layer 详解 Android Systrace 基础知识 -- 分析 Systrace 预备知识 Android Systrace 基础知识 - SystemServer 解读 Android Systrace 基础知识 -- Systrace 简介 Android Systrace 基础知识 -- Why 60 fps ? Android Systrace -- 系列文章目录 Android 新的流畅体验,90Hz 漫谈 利器 - 高效工具推荐 Android 无障碍服务导致的整机卡顿案例分析 2018 年度好物推荐 - 给辛勤工作的自己一点奖励 Android 系统开发源码环境搭建 陆奇:除了好代码,工程师怎样才算优秀? 程序员的修炼-08-阅读之美 程序员的修炼-07-游戏与编程 程序员的修炼-06-互联网那些事 程序员的修炼-05-了解你的用户 程序员的修炼-04-关于测试的一些思考 程序员的修炼-03-Web 设计原则 程序员的修炼-02-编程之道 程序员的修炼-01-绝地反击之术 Android 系统不释放内存吗? 关于 Android 系统流畅性的一些思考 知乎 救救你的 StartingWindow 「置顶」Android 性能优化必知必会 2017 年度好物推荐 - 给辛勤工作的自己一点奖励 2017 Android Bottom navigation 规范二:样式、行为与规格 Android Bottom Navigation 规范一:使用方法 Android 中如何计算 App 的启动时间? Android 应用启动优化:一种 DelayLoad 的实现和原理(下篇) Android 应用启动优化 - 一种 DelayLoad 的实现和原理(上篇) Android hwui 中 RenderThread 工作流程 Java7 HashMap 源码分析 Android 代码内存优化建议 - OnTrimMemory 优化 Android 代码内存优化建议 - Android 资源篇 Android 代码内存优化建议 - Android 官方篇 Android 代码内存优化建议 - Java 官方篇 Nexus6 with Android M 开启多窗口模式 细说 Java 单例模式 Android 性能优化典范 - Profile GPU Rendering Android 性能优化典范 - Understanding VSYNC
一个「闰」字引发的事故 - 三星系统重启分析
Gracker · 2020-05-26 · via Android Performance

2020 年 5 月 23 号凌晨 1 点 30 左右, 大量三星手机用户的手机出现死机, 无限重启、进 Recovery 等问题, 并且操作不当会导致数据丢失, 并且上了知乎的热点, 售后点更是人满为患

知乎的部分回答中, 大家更是对三星的家属送上了亲切的问候, 甚至有的人已经将这次事故与 Note7 事件、充电门、绿屏门事件相提并论, 甚至预言三星因此会退出国内市场 ; 有的人因为这个丢了 Offer, 有的人准备了很久的资源丢失, 有的人甚至直接把手机砸了…

作为一个 Android 开发者, 我并不想对三星落井下石, 我只想搞清楚到底是什么原因导致了这场事故, 以及我们能从里面学到什么 . 我认为既然是 Android 系统出了问题, 我们有必要从技术的角度来分析为什么会出现这样的问题

知乎热点

甚至商场里的机器都变砖了

商场

结论

结论先行, 对于不喜欢看长文的吃瓜群众来说, 直接看结论即可:

这次事故表现是一部分三星用户的手机系统中关键系统服务重复 Crash 并强制进入 Recovery 界面. 关键系统服务指的是三星的 SystemUI 进程, SystemUI 进程在初始化 AOD 插件的时候 AOD 插件初始化出错导致 SystemUI Crash, 由于是系统服务, SystemUI Crash 到一定的次数之后, 就会强制进入 Recovery 界面, 所以 大部分用户看到的都是 Recovery 界面(下面有图)

AOD 全称 Always On Display, 中文翻译是息屏显示, 就是你按电源键锁屏后, 在屏幕上显示时间、天气、图案等的服务, 这个只有部分高端机型才有这个功能.

AOD Crash 的原因是 2020 年 5 月 23 是闰四月, AOD 显示阴历的时候, 需要显示闰四月, 所以在代码中会走到显示闰四月这个一般很难走进的分支条件, 走进这个条件之后, 需要获取 common_data_leap_month 这个字段, 但是由于代码编译出现了 Bug, 导致无法找到这个字段, 所以该进程直接报了 FATAL EXCEPETION, 进程重启, 重启之后还是要获取这个字段, 再重启, 如此反复, 最终触发系统的自救措施, 进入 Recovery 界面

这也是为什么只有中国用户才会出现这个问题, 就是因为 AOD 在 5 月 23 号需要显示”闰”四月, 但是没找到 “闰” 这个字, 所以就挂了 . 所以并不是千年虫, 也不是服务器被黑, 更不是三星故意恶心人, 这种编译导致的 Bug, 再碰上几年一遇的闰月, 遇到了就认了吧, 老老实实道歉, 不丢人.

分析

吃瓜群众可以折返了, 感兴趣的 Android 开发者可以继续往下看, 内容虽然简单, 但是个人觉得还是可以看一下的

对于三星的开发人员来说, 分析这个 Crash 非常简单, 直接在监控里面捞 Log 就可以了, 从后面的分析来看, 这个问题也很快被发现, 并进行了修复(持续了半年左右);但是对这个问题感兴趣的其他开发者来说, 需要借助其他的工具

不过分析的过程也非常简单, 这里会把自己分析的思路和用到的工具记录下来, 方便大家使用

从现象入手

上面结论有说, Persist 进程频繁 Crash 会导致系统触发自救, 进入 Recovery 界面, 所以用户很多反馈截图大家看到都是 Recovery 界面, 如下
Recovery 界面

不过也有用户的界面直接显示了报错信息(我猜测是三星这边自己加的功能吧, 知道的麻烦告知一下), 这个界面对我们分析代码来说很重要

报错信息界面

开发者对于这个堆栈是最熟悉不过了, 这是在一帧的渲染流程中, AOD 的 LocalDataView 在初始化的时候, 调用 getLunarCalendarInChina 方法出错了, LunarCalendar 是阴历的意思, 报错主要是因为找不到 common_data_leap_month 这个 string 值.

那么问题就很清楚了, 我们只需要查下面两个点

  1. common_data_leap_month 这个 string 字段出现的代码逻辑
  2. common_data_leap_month 这个 string 字段没有找到导致运行报错的原因

分析代码

首先看 common_data_leap_month 字段出现的代码逻辑, 既然上面已经列出了函数堆栈, 那么我们需要直接查看代码来分析这个问题产生的逻辑, 如何拿到代码?自然是需要反编译, 推荐的反编译工具: TTDeDroid

反编译需要三星 AOD 的代码, 可以在 ApkMirror 里面搜 Always-On-Display, 就可以找到对应的文件, 可以看到三星的 AOD 更新的频率还是很频繁的, 通过用户反馈可以知道, 并非所有的用户都有这个问题, 且更新到新版本就没有问题了, 那么我们推测问题是出在老版本上的( 从堆栈来猜测应该是 V4.0 的版本 )

正常版本_V5.2.05

最新版本是正常的, 没有 Crash 的情况

首先我们先看一下最新版本这一段代码的逻辑

1
String month = shouldShowLeapMonth(locale) ? context.getResources().getString(R.string.common_date_leap_month) + months[convertMonth] : months[convertMonth]

这个就是说如果需要显示闰月, 那就取 common_date_leap_month 的值, 全局搜索 common_date_leap_month 发现最新版本里面是有定义这个值的 .

这里就可以看到 common_data_leap_month 字段出现的代码逻辑 : 只有需要显示闰月的时候, 才会去获取 common_date_leap_month 这个字段的值, 其他 99.9% 的时候都不会触发这个值的获取 .

代码逻辑

R.java 文件里面存在的 common_date_leap_month, 说明是存在的, 查看对应的 string.xml 中也有这个字段的定义

R 文件

xml 文件

出问题版本_V4.1.70

既然新版本没有问题, 且我们也知道了 common_date_leap_month 这个字段的代码逻辑, 那么我们从老版本来看 common_date_leap_month 这个字段没有找到的原因.

这里找的这个老版本是有问题的, 使用这个版本(这几个版本) 的用户到了 23 号会出现频繁 Crash 的现象. 之所以我认为他是有问题的, 是因为全局搜索 common_date_leap_month 字段, 发现 R 文件里面没有对应的字段, 对应的 string.xml 里面也没有这个字段和他对应的值, 也就是说, 这里代码只使用, 没有定义和赋值( 那怎么编译过的呢 ???)

只有使用,没有声明和赋值

上面对应的代码逻辑如下, 可以看到函数名和行数和报错是一致的, InChina….

对应的代码和行数

具体对应的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private String getLunarCalendarInChina(Context context) {
if (sSolarLunarConverter == null) {
sSolarLunarConverter = SECCalendarFeatures.getInstance().getSolarLunarConverter();
if (sSolarLunarConverter == null) {
return "";
}
}
Time time = new Time();
time.set(Calendar.getInstance().getTimeInMillis());
sSolarLunarConverter.convertSolarToLunar(time.getYear(), time.getMonth(), time.getMonthDay());
String[] months = context.getResources().getStringArray(R.array.common_LunarMonth);
String[] days = context.getResources().getStringArray(R.array.common_LunarDay);
int convertMonth = sSolarLunarConverter.getMonth();
int convertDay = sSolarLunarConverter.getDay() - 1;
ACLog.d(TAG, "Lunar month and day : " + convertMonth + ", " + convertDay);
if (convertMonth < 0 || convertMonth >= months.length || convertDay < 0 || convertDay >= days.length) {
ACLog.e(TAG, "getLunarCalendarInChina, array out of bound month = " + months.length + ", days = " + days.length);
return "";
}
String chinaLunar = (sSolarLunarConverter.isLeapMonth() ? context.getResources().getString(R.string.common_date_leap_month) + months[convertMonth] : months[convertMonth]) + days[convertDay];
String str = chinaLunar;
return chinaLunar;
}

问题出现的时间

根据我这边的调查, 发现这个问题其实在 AOD 这个应用从 v3.3.18 升级到 v4.0.57 的时候就出现了, 但是中间一直都没有出问题, 没有闰四月, 用户也就不会有问题, 测试也没有测出来, 直到 2019 年 6 月 24 号发布的 v4.2.44 版本才修复了这个问题

4.3.44 版本修复

v3.3.18 版本我们可以看到, common_date_leap_month 这个字段还是存在的

v3.3.18

升级到 v4.0.57(第一个出问题的版本) 之后, 这个字段就没有了( 那怎么编译过的呢 ???)

理一下

  1. AOD 从 v3.3.18 升级到 v4.0.57 的引入了这个问题(2018 年 10 月 24 号引入)
  2. AOD 从 v4.2.24 升级到 v4.2.44 解决了这个问题(2019 年 6 月 24 号 修复)

这期间所有 AOD 版本在 v4.0.57 - v4.2.44 却从来没有升级的机型, 都会在 2020-5-23 号这一天进入 Recovery 模式.

编译问题

上面一个很重要的点就是编译问题, Android 开发者都知道, 如果我在代码中写 getString(R.string.common_date_leap_month), 那我得在 strings.xml 里面定义这个 common_date_leap_month, 然后给他赋值, 比如 “闰”, 这样才能在 R 文件中看到这个字段, 我们才能使用 getString(R.string.common_date_leap_month) 这样的语法去调用 ; 否则在编译阶段就会出现问题, 编译提示 R.string.common_date_leap_month 不存在

罪魁祸首

但是通过上面的分析我们发现, 频繁 Crash 的版本就是因为找不到 common_date_leap_month 这个字段才 Crash 的, 既然找不到那也应该编译不过才对, 但是既然我们拿到了 apk, 那说明编译也是没问题的.

这种情况出现的话, 一般有下面两种情况

  1. 项目中有同一个 jar 包的不同版本, 因此编译和运行时使用了不同的 jar 包
  2. 编译使用的是 Maven, 项目中的依赖由于使用了不同版本的包, 最后打包的时候使用的不是你需要的版本

猜测三星这次出问题的是因为第二种情况, 主项目和子 modules 使用了不同版本的包, 导致可以编译通过, 但是最终打包进项目的并不是编译时候的包, 就出现了运行时的 FATAL EXCEPTION : NoSuchFieldError ( 如果有知道具体原因的可以留言讨论一波 )

开个玩笑, 这个问题对三星来说绝对是一个大的事故, 不过也贡献了一个经典的案例, 估计以后其他 App 或者手机厂商都会把这个纳入到功能测试中. 至于三星, 国内市场本身就不行了, S20 系列刚有些回暖, 又出现这档子事, 还是那句话 : 这是命, 得认, 道歉, 不丢人

想必三星对这一天也会铭心刻骨

总结

上面的分析过程虽然比较简单, 但是有一些比较繁琐的工作, 比如找版本, 反编译, 看代码逻辑等. 最终也算是找到了问题的根本原因 : 编译导致的问题碰上十几年才遇到一次的闰四月. 那么从这件事我们学到了什么呢?

  1. 功能测试 : 闰月是日历中的一个功能, 不算是常用功能, 但是相对来说比较专业, 像这种涉及到专业的地方, 一定要谨慎, 列出所有可能出现的情况去做测试, 必要的情况下, 交给专业的人来评估测试用例
  2. 涉及到多方依赖编译的项目, 在编译的时候要确保引用的版本和本地的版本一致, 对于多方依赖的模块, 每次发版本之前最好跟对应的依赖的模块确认
  3. SystemUI (锁屏\状态栏\手势\多任务\ AOD 等) 模块和桌面模块是用户直接能感受到的模块, 这些模块对稳定性的要求要非常高, 因为一旦这些模块发生 FATAL, 带来的影响是非常巨大的, 就像三星这次, 所以这几个模块的开发人员也是最辛苦的, 既要承接一些亮点功能的实现, 又要保证稳定性, 同时也位于系统开发和应用开发中间, 两边都有很大的耦合, 着实不容易 (媳妇做这一块 6 年多了, 晚上加个鸡腿…)
  4. 厂商提供的系统更新和厂商自己的应用更新(尤其是系统应用), 一定要及时更新, 每次系统和系统应用更新一般都会修复很多 Bug, 增强稳定性和性能. 系统和系统应用没有盈利的压力, 所以更新都是以提升质量为主, 可以放心更新.
  5. 开发者对这种事情要保持好奇和敬畏 : 好奇可以帮助我学到很多东西, 透过现象看本质 ; 敬畏可以让我知道自己知识的欠缺, 在庞大的 Android 体系中, 自己知道的不过沧海一粟..
  6. 这个问题对三星来说绝对是一个大的事故, 不过也贡献了一个经典的案例, 估计以后其他 App 或者手机厂商都会把这个纳入到功能测试中. 至于三星, 国内市场本身就不行了, S20 系列刚有些回暖, 又出现这档子事, 还是那句话 : 这是命, 得认, 道歉, 不丢人」

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球 : 欢迎加入,多谢支持~

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫