iOS 启动连续闪退保护方案

一.引言

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

为了尝试解决这个问题,微信读书开发了 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),但是可以通过设置阈值来减小误报的误报率。

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

三.连续闪退修复

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

1.闪退原因

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

  1. 数据库损坏:在日常使用如异常退出、断电,或者错误的操作(参考:sqlite corruption causes)。
  2. 文件损坏:处理文件时如果没有 @try...catch,损坏文件会抛出 NSException 导致 crash
  3. 网络返回数据处理异常:比如预期返回数组,但实际返回了字典,对字典对象执行 -objectAtIndex 方法会产生 crash: unknow selector send to object;,或返回破损的 Tar 包,在解压失败导致 crash。
  4. 代码 bug:当必 crash 的代码出现在启动关键路径中,就会导致连续闪退。
  5. 针对 1,可以通过工具修复数据库,或者删除 DB。针对2,可以删除文件来进行修复。对于 3 和 4,我们需要具体地分析 crash 案例,通过 JSPatch 来进行修复。

2.微信读书的修复流程

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

  1. 进入 didFinishLaunch 时检查是否有连续闪退,无则执行 5
  2. 弹 Toast 提示用户是否修复,轻触『修复』执行2,否则执行 5
  3. 尝试下载并执行 JSPatch 补丁 这里是为了解决上述第4点 - 代码 bug 导致的闪退,使用 JSPatch [github]可以进行热修复。在 didFinishLaunching 时,会卡住界面发请求检查是否有可用的 JSPatch 脚本,如果有则加载执行,解决代码 bug 导致的闪退。
  4. 尝试删除Documents /Library / Caches 目录下的所有文件
  5. 这里直接删除了所有用户数据,适用于微信读书这种所有数据都在云端,删除后可以完全从云端恢复。如果你的 App 不属于这种场景,那么应该在 repairBlock 中自定义修复逻辑,比如: a. 不删除文件,只修复数据库 b. 修复前把用户数据备份到云端 c. 收集 crash 样本,查明原因,定制 JSPatch 修复补丁并下发
  6. 退出微信读书登录状态
  7. 进入原 didFinishLaunch 连续闪退检测 + 保护流程如图所示:

3.实现

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

直接修改 -application:didFinishLaunchingWithOptions: 方法。 新建一个 SubAppDelegate 类来继承 AppDelegate,覆盖 -application:didFinishLaunchingWithOptions: 方法,然后把 main() 函数中的 AppDelegate 替换为 SubAppDelegate 新建一个 AppDelegate 扩展,然后用 method swizzle 的方法替换 -application:didFinishLaunchingWithOptions: 方法。 上述三种方案,对现有项目改动代价是 1 > 2 > 3。因此,我们使用对源码修改代价最小的方案 3 来替换-application:didFinishLaunchingWithOptions:

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

4.使用

引入项目

  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 }]; }
  3. 在 onBootingProtection 方法中添加修复逻辑,比如删除文件: (void)onBootingProtection { // 检查 JSPatch 更新 ... // 删除 Documents Library Caches 目录下所有文件 [GYBootingProtection deleteAllFilesUnderDocumentsLibraryCaches]; ... } 如需执行异步的修复逻辑,在 onBootingProtectionWithCompletion: 方法添加修复逻辑,并在完成修复后调用 completion : (void)onBootingProtectionWithCompletion:(BoolCompletionBlock)completion { [self onBootingProtection]; // 异步修复 [self asyncRepairWithCompletion:^(void) { // 正常启动流程 if (completion) completion(); }]; }

5.测试

  1. 首先制造连续闪退场景: 启动后 5 秒内,双击 Home 通过上划手势 kill 掉 App,重复多次。(也可以在代码里人为制造crash)
  2. 当连续闪退超过 5 次时,会提示用户修复:
  1. 用户轻触修复,App 重置初始状态,连续闪退问题解决:

源码 https://github.com/liuslevis/GYBootingProtection

相关推荐

微信读书排版引擎自动化测试方案

手游兼容性测试 产品简介

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java3y

操作系统第六篇【存储器管理】

3677
来自专栏张善友的专栏

Windows Server AppFabric Caching

这套 AppFabric Caching 比我用过的 memcached 复杂多了,MSDN有一篇文章进行介绍Introduction to Caching w...

2089
来自专栏WindCoder

iOS集中和解耦网络:具有单例类的AFNetworking教程

当涉及iOS架构模式时,模型 - 视图 - 控制器(MVC)设计模式对于应用程序的代码库的长寿和可维护性是非常有用的。通过将它们解耦从而使类可以很容易地被重用或...

1211
来自专栏程序猿DD

优雅处理你的Java异常

来源:https://my.oschina.net/c5ms/blog/1827907

1772
来自专栏君赏技术博客

最基本的调试是NSLog及DEBUG预处理器宏

在系统控制台显示日志信息运行应用程序时是最早调试机制之一,利用log你可以查看应用程序的运行记录,当程序运行完毕,你可以长时间查看。此外,您的应用程序运行期间,...

953
来自专栏葡萄城控件技术团队

Winform文件下载之WebClient

最近升级了公司内部使用的一个下载小工具,主要提升了下面几点: 1. 在一些分公司的局域网中,连接不上外网 2. 服务器上的文件更新后,下载到的还是更新前的文件 ...

1965
来自专栏安恒网络空间安全讲武堂

翻译 | python利用shodan搜集信息

文中提及的部分技术、工具可能带有一定的攻击性、仅供安全学习和教学用途,禁止非法使用! 安装 为了开始使用Shodan的Python库,首先要确保你已经收到了AP...

42810
来自专栏生信技能树

可能是最全的JBrowse基因浏览器介绍

日常工作的窘境 谈基因浏览器的必要性,不需要扯“各种基因组序列以及高通量测序数据爆炸性增长,满足基因组可视化、大规模基因组数据分析和应用需要”这些有的没的,只需...

5477
来自专栏逸鹏说道

bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序

bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序 也许单页程序(Single Pa...

2845
来自专栏H2Cloud

Future Pattern

Started: 俗话说一年之计在于春,一天之计在于晨,当我起床的时候,看见表正指向九点钟,十一点下班,十点上班,这是我现在的工作节奏。来北京马上就一个月了,近...

3495

扫码关注云+社区