版权声明:本文由刘笑江原创文章,转载请注明出处:
文章原文连接:https://www.qcloud.com/community/article/79html
来源:腾云阁 https://www.qcloud.com/communitygit
“若是某个实体表现出如下任何一种特性,它就具有自主性:自我修复、自我保护、自我维护、对目标的自我控制、自我改进。” —— 凯文·凯利github
iOS App 有时可能遇到启动必 crash 的绝境:每次打开 App 都闪退,没法正常使用App。sql
为了尝试解决这个问题,微信读书开发了 iOS 连续闪退保护工具:GYBootingProtection,检测连续闪退,在连续闪退出现时,尝试自修复 App:数据库
本文探讨了连续闪退问题的产生缘由、检测、修复机制,以及如何在你的项目中引入、测试和使用 GYBootingProtection。编程
首先要检测用户 App 出现了连续闪退的状况,有两种检测方法,捕获异常和计时器。数组
检测连续闪退,能够经过捕获异常来实现,异常有如下种类:微信
在念茜的漫谈 iOS Crash 收集框架一文中详细介绍了 Mach 异常和 Unix 信号捕获 crash 的机制。简单来讲,异常通常产生自 iOS 的微内核 Mach,而后在 BSD 层转换成 UNIX SIGABRT 信号,以标准 POSIX 信号的形式提供给用户。NSException 是使用者在处理 App 逻辑时,用编程的方法抛出。网络
经过如下方法捕获异常:app
Crash 上报工具如 PLCrashReporter 经过注册 Mach 异常 + UNIX信号 的 handler 达到检测的目的,对用户提供了处理异常的接口。
能够利用 PLCrashReporter 这类工具来检测连续闪退:
经过 Mach 异常、Unix 信号、NSException 异常来检测闪退,能得到更多的 crash 上下文,但因为 crash 收集框架多使用这些方法,可能会有这样的风险:与第三方 crash 收集框架冲突致使漏检测。另外,可能会与 App 已有的异常处理代码产生耦合。
除了经过捕获异常的方式检测连续闪退,还能够经过计数器方法来检测:
而计数器方法逻辑简单,与原有的代码耦合小。虽然有误报可能(在启动后当即被 kill 掉,误认为 crash),可是能够经过设置阈值来减少误报的误报率。
综上权衡,咱们使用计时器方法检测连续闪退。
检测到连续闪退后,接下来要尝试对闪退进行修复,这里先分析可能的闪退缘由,再结合微信读书的例子说明修复流程。
连续闪退,多是 App 启动关键路径中执行了必 crash 的代码,缘由可能有:
@try...catch
,损坏文件会抛出 NSException
致使 crash-objectAtIndex
方法会产生 crash: unknow selector send to object
;,或返回破损的 Tar 包,在解压失败致使 crash。为了应对上述致使连续闪退的缘由,微信读书的修复流程为:
尝试下载并执行 JSPatch 补丁
这里是为了解决上述第4点 - 代码 bug 致使的闪退,使用 JSPatch [github]能够进行热修复。在 didFinishLaunching 时,会卡住界面发请求检查是否有可用的 JSPatch 脚本,若是有则加载执行,解决代码 bug 致使的闪退。
尝试删除Documents
/Library
/ Caches
目录下的全部文件
这里直接删除了全部用户数据,适用于微信读书这种全部数据都在云端,删除后能够彻底从云端恢复。若是你的 App 不属于这种场景,那么应该在 repairBlock 中自定义修复逻辑,好比:
a. 不删除文件,只修复数据库
b. 修复前把用户数据备份到云端
c. 收集 crash 样本,查明缘由,定制 JSPatch 修复补丁并下发
退出微信读书登陆状态
进入原 didFinishLaunch
连续闪退检测 + 保护流程如图所示:
检测和连续 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 便可。
引入项目
下载 (github) 源码 ,将 src 目录下全部文件拖拽到你的 Xcode 项目
在 AppDelegate+GYBootingProtection.m
的 onBeforeBootingProtection
方法中添加检测前须要执行的代码,好比设置crash上报:
(void)onBeforeBootingProtection { [GYBootingProtection setLogger:^(NSString *msg) { // setup logger NSLog(@"%@", msg); }]; [GYBootingProtection setReportBlock:^(NSInteger crashCounts) { // setup crash report }]; }
在 onBootingProtection 方法中添加修复逻辑,好比删除文件:
(void)onBootingProtection { // 检查 JSPatch 更新 ... // 删除 Documents Library Caches 目录下全部文件 [GYBootingProtection deleteAllFilesUnderDocumentsLibraryCaches]; ... }
如需执行异步的修复逻辑,在 onBootingProtectionWithCompletion
: 方法添加修复逻辑,并在完成修复后调用 completion :
(void)onBootingProtectionWithCompletion:(BoolCompletionBlock)completion { [self onBootingProtection]; // 异步修复 [self asyncRepairWithCompletion:^(void) { // 正常启动流程 if (completion) completion(); }]; }
首先制造连续闪退场景:
启动后 5 秒内,双击 Home 经过上划手势 kill 掉 App,重复屡次。(也能够在代码里人为制造crash)
当连续闪退超过 5 次时,会提示用户修复:
用户轻触修复,App 重置初始状态,连续闪退问题解决: