QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

iOS 启动连续闪退保护方案

  • 2016-06-22
  • 本文字数:3094 字

    阅读完需:约 10 分钟

引言

“如果某个实体表现出以下任何一种特性,它就具备自主性:自我修复、自我保护、自我维护、对目标的自我控制、自我改进。” —— 凯文·凯利

iOS App 有时可能遇到启动必 crash 的绝境:每次打开 App 都闪退,无法正常使用 App。

为了尝试解决这个问题,微信读书开发了 iOS 连续闪退保护工具: GYBootingProtection ,检测连续闪退,在连续闪退出现时,尝试自修复 App:

本文探讨了连续闪退问题的产生原因、检测、修复机制,以及如何在你的项目中引入、测试和使用 GYBootingProtection

连续闪退检测

首先要检测用户 App 出现了连续闪退的情况,有两种检测方法,捕获异常和计时器。

1. 捕获异常

检测连续闪退,可以通过捕获异常来实现,异常有以下种类:

  • Mach 异常:EXC_CRASH
  • UNIX 信号:SIGABRT
  • NSException 异常:应用层,通过 NSUncaughtExceptionHandler 捕获

在念茜的漫谈 iOS Crash 收集框架一文中详细介绍了 Mach 异常和 Unix 信号捕获 crash 的机制。简单来说,异常一般产生自 iOS 的微内核 Mach,然后在 BSD 层转换成 UNIX SIGABRT 信号,以标准 POSIX 信号的形式提供给用户。NSException 是使用者在处理 App 逻辑时,用编程的方法抛出。

如何捕获异常

通过以下方法捕获异常:

  • 利用 Mach API 捕获 Mach 异常
  • 通过 POSIX API 注册 signal(SIGSEGV,signalHandler) 来捕获 UNIX 异常信号
  • 注册 NSUncaughtExceptionHandler 来捕获应用级异常

Crash 上报工具如 PLCrashReporter 通过注册 Mach 异常 + UNIX 信号 的 handler 达到检测的目的,对用户提供了处理异常的接口。

如何检测

可以利用 PLCrashReporter 这类工具来检测连续闪退:

  1. 首先维护一个计数变量,表示连续闪退次数
  2. 在 PLCrashReporter 的 crash handler 中加入逻辑:如果启动 5s 内 crash 使计数器加一
  3. 每次启动时,如果连续闪退计数 > n,则检测到了连续闪退
  4. 启动后,执行一个定时任务,在 5s 后重置计数(如果 App 连续闪退则不会重置)

流程图

优缺点

通过 Mach 异常、Unix 信号、NSException 异常来检测闪退,能获得更多的 crash 上下文,但由于 crash 收集框架多使用这些方法,可能会有这样的风险:与第三方 crash 收集框架冲突导致漏检测。另外,可能会与 App 已有的异常处理代码产生耦合。

2. 计时器方法

除了通过捕获异常的方式检测连续闪退,还可以通过计数器方法来检测:

  1. 维护一个计数变量,用于表示连续闪退的次数
  2. 在启动 application:didFinishLaunchingWithOptions: 后使计数加一
  3. 接着使用 dispatch_after 方法在 5s 后清零计数,如果 App 活不过 5 秒计数就不会被清零
  4. 如果发现计数变量 > n,表明 App 连续 n 次连续闪退,启动保护流程,重置计数。
  5. 当保护流程完成后,进入 App 正常启动流程

流程图

优缺点

而计数器方法逻辑简单,与原有的代码耦合小。虽然有误报可能(在启动后立即被 kill 掉,误认为 crash),但是可以通过设置阈值来减小误报的误报率。

综上权衡,我们使用计时器方法检测连续闪退。

连续闪退修复

检测到连续闪退后,接下来要尝试对闪退进行修复,这里先分析可能的闪退原因,再结合微信读书的例子说明修复流程。

闪退原因

连续闪退,可能是 App 启动关键路径中执行了必 crash 的代码,原因可能有:

  1. 数据库损坏:在日常使用如异常退出、断电,或者错误的操作(参考: sqlite corruption causes )。
  2. 文件损坏:处理文件时如果没有 @try...catch,损坏文件会抛出 NSException 导致 crash
  3. 网络返回数据处理异常:比如预期返回数组,但实际返回了字典,对字典对象执行 -objectAtIndex 方法会产生 crash: unknow selector send to object;,或返回破损的 Tar 包,在解压失败导致 crash。
  4. 代码 bug:当必 crash 的代码出现在启动关键路径中,就会导致连续闪退。

针对 1,可以通过工具修复数据库,或者删除 DB。针对 2,可以删除文件来进行修复。对于 3 和 4,我们需要具体地分析 crash 案例,通过 JSPatch 来进行修复。

微信读书的修复流程

为了应对上述导致连续闪退的原因,微信读书的修复流程为:

  1. 进入 didFinishLaunch 时检查是否有连续闪退,无则执行 5
  2. 弹 Toast 提示用户是否修复,轻触『修复』执行 2,否则执行 5
  3. 尝试下载并执行 JSPatch 补丁

这里是为了解决上述第 4 点 - 代码 bug 导致的闪退,使用 JSPatch [github] 可以进行热修复。在 didFinishLaunching 时,会卡住界面发请求检查是否有可用的 JSPatch 脚本,如果有则加载执行,解决代码 bug 导致的闪退。
4. 尝试删除 Documents / Library / Caches 目录下的所有文件

这里直接删除了所有用户数据,适用于微信读书这种所有数据都在云端,删除后可以完全从云端恢复。如果你的 App 不属于这种场景,那么应该在 repairBlock 中自定义修复逻辑,比如:

a. 不删除文件,只修复数据库
b. 修复前把用户数据备份到云端
c. 收集 crash 样本,查明原因,定制 JSPatch 修复补丁并下发
5. 退出微信读书登录状态
6. 进入原 didFinishLaunch

连续闪退检测 + 保护流程如图所示:

实现

检测和连续 crash 并修复需要修改原 -application:didFinishLaunchingWithOptions: 逻辑,有几种方法:

  1. 直接修改 -application:didFinishLaunchingWithOptions: 方法。
  2. 新建一个 SubAppDelegate 类来继承 AppDelegate,覆盖 -application:didFinishLaunchingWithOptions: 方法,然后把 main() 函数中的 AppDelegate 替换为 SubAppDelegate
  3. 新建一个 AppDelegate 扩展,然后用 method swizzle 的方法替换 -application:didFinishLaunchingWithOptions: 方法。

上述三种方案,对现有项目改动代价是 1 > 2 > 3。因此,我们使用对源码修改代价最小的方案 3 来替换 -application:didFinishLaunchingWithOptions:

检测的逻辑 GYBootingProtection 已经处理好,修复的处理预留了接口,可以由用户自定义,把自定义的修复流程传入 repairBlock 即可。

使用

引入项目

  1. 下载 (github) 源码 ,将 src 目录下所有文件拖拽到你的 Xcode 项目
  2. AppDelegate+GYBootingProtection.monBeforeBootingProtection 方法中添加检测前需要执行的代码,比如设置 crash 上报:
复制代码
- (void)onBeforeBootingProtection {
[GYBootingProtection setLogger:^(NSString *msg) {
// setup logger
NSLog(@"%@", msg);
}];
[GYBootingProtection setReportBlock:^(NSInteger crashCounts) {
// setup crash report
}];
}<br></br>
  1. onBootingProtection 方法中添加修复逻辑,比如删除文件:
复制代码
- (void)onBootingProtection {
// 检查 JSPatch 更新
...
// 删除 Documents Library Caches 目录下所有文件
[GYBootingProtection deleteAllFilesUnderDocumentsLibraryCaches];
...
}<br></br>

如需执行异步的修复逻辑,在 onBootingProtectionWithCompletion: 方法添加修复逻辑,并在完成修复后调用 completion :

复制代码
- (void)onBootingProtectionWithCompletion:(BoolCompletionBlock)completion {
[self onBootingProtection];
// 异步修复
[self asyncRepairWithCompletion:^(void) {
// 正常启动流程
if (completion) completion();
}];
}<br></br>

测试

  1. 首先制造连续闪退场景:

启动后 5 秒内,双击 Home 通过上划手势 kill 掉 App,重复多次。(也可以在代码里人为制造 crash)
2. 当连续闪退超过 5 次时,会提示用户修复:


3. 用户轻触修复,App 重置初始状态,连续闪退问题解决:

源码

https://github.com/liuslevis/GYBootingProtection

查看原文: iOS 启动连续闪退保护方案


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-06-22 18:565232

评论

发布
暂无评论
发现更多内容

【LeetCode】统计匹配检索规则的物品数量Java题解

Albert

算法 LeetCode 11月月更

成为千行百业数字化转型催化剂的,竟然是它!

元年技术洞察

微服务 低代码 数字化转型

首批招募 50 家!「龙腾社区生态发展计划」正式发布

OpenAnolis小助手

开源 操作系统 云栖大会 龙蜥社区 合作

消息队列 RocketMQ 5.0:从消息服务到云原生事件流平台

阿里巴巴云原生

阿里云 RocketMQ 云原生

【web 开发基础】PHP 自定义函数之函数的返回值-PHP 快速入门 (27)

迷彩

web开发基础 PHP基础 11月月更 return

软件测试 | 测试开发 | 校招面试真题 | 实习生和应届生有什么区别?

测吧(北京)科技有限公司

软件测试 软件测试工程师

AOP 的九点核心概念和作用

千锋IT教育

PCB设计必须考虑的8种安全距离,搞错1种都出大问题!

华秋PCB

PCB PCB设计

RocketMQ 重试机制详解及最佳实践

阿里巴巴云原生

阿里云 RocketMQ 云原生

Meta Force 原力元宇宙公排系统开发详情

开发微hkkf5566

Wallys/ WiFi6 MiniPCIe Module 2T2R 2×2.4GHz 2x5GHz MT7915 MT7975 /industrial mini pcie card

wallysSK

MT7915

MetaForce佛萨奇2.0系统开发DAPP搭建

薇電13242772558

dapp开发

使用 Fiori Elements 框架创建 UI5 Web 应用

汪子熙

web开发 Fiori SAP UI5 ui5 11月月更

Python图像处理丨5种图像处理特效

华为云开发者联盟

Python 人工智能 华为云 图像处理

离职、被毕业?职场打工人的最强生存指南!

千锋IT教育

PolarDB-X 开源分布式数据库进阶营免费报名中!

阿里云数据库开源

MySQL 数据库 阿里云 开源 PolarDB-X

前端培训班中如何学习前端开发技术

小谷哥

Spring 5(五)事务操作

浅辄

Spring5 事务 11月月更

自学前端技术怎么样,有必要去吗

小谷哥

大数据培训学习需要什么基础

小谷哥

终于有人把这份10 万字节详细面试笔记(带完整目录) 整理出来了

钟奕礼

Java java程序员 java面试 java编程 Java 面试题

低门槛上手快!火山引擎VeDI这样满足数据分析新需求

字节跳动数据平台

大数据 BI

Dive into TensorFlow系列(3)- 揭开Tensor的神秘面纱

京东科技开发者

Python 人工智能 深度学习 tensorflow

大数据培训和自学哪种方式更好

小谷哥

腾讯T4带你玩转Spring全家桶

钟奕礼

Java java程序员 java面试 java编程

5种GaussDB ETCD服务异常实例分析处理

华为云开发者联盟

数据库 后端 华为云

声网深度学习时序编码器的资源预测实践丨Dev for Dev 专栏

声网

深度学习 算法 模型

深究并行编程Parallel类中的三大方法 (For、ForEach、Invoke)和几大编程模型(SPM、APM、EAP、TAP)

C++后台开发

多线程 后端开发 linux开发 C++开发 并行编程

灵雀云ACP 斩获“2022金边奖-最佳云原生边缘云平台”

York

容器 云原生 5G 边缘计算 边缘云

极客时间架构训练营模块六作业

李晨

架构

微服务中的服务发现是什么?

API7.ai 技术团队

微服务 服务发现 API网关 APISIX

iOS启动连续闪退保护方案_移动_刘笑江_InfoQ精选文章