崩溃治理的重要性体现在两个维度:
- 从拉新成本上看,一个用户的成本大概是80块,以100万用户*千分之五的崩溃率得出受影响的用户数为5000人,如果这些用户流失将直接损失40万。
- 从时间成本计算,稿定的模块搜索、编辑器、下单支付等链路的最短操作时间要 120s 左右,遇到崩溃后用户需要重新打开 App,假设第二次不崩溃,那么对100万用户来说,千分之五的崩溃率意味每天要浪费掉 600000s,也就是近 6.9天,一年就要浪费掉 2534天。
对移动端来说,能导致崩溃的原因太多了:NPE、数组下标越界、ORM、文件 IO、OOM 等等,如果不能做系统性的治理,很难压制住崩溃率。
事前防范
事前防范是指在发布前最大程度的减少崩溃概率,常见的几种工具有:
- 静态分析 - 对 NPE、数组下标越界等常规崩溃很有用。
- 云测平台 - 对碎片化严重的 Android 机型很有用,因为很难通过人工发现所有机型存在的问题,如果能借助云测平台完成自动化测试,告诉我们有没有崩溃、哪里崩溃了,并以此完成机型的适配工作,将会减少很多意想不到的崩溃发生。
除了工具,做有益于崩溃治理的架构也是很重要的事情。崩溃携带的信息就是指引我们找到原因的线索,合理的工程架构可以有效的定位崩溃,反之不合理的工程架构如同迷宫一般,不要说定位崩溃了,日常维护都很困难:
如同地图和迷宫的区别。
以 ORM 产生的崩溃为例,如果因为字段类型或属性不存在造成了崩溃,那么我们只需要在统一的 ORM 工具类层面做好相应的容错和检查,那这一类问题基本上都能全都得到解决,在配合 lint 工具对接入环节做自动化的检测,如果有工程师使用不当就会报错,以此让所有人遵循相同的规范降低错误发生的概率。
事中记录
目前移动端采用 Bugly 追踪线上崩溃问题,配合可观测性项目可以对这类问题做到比较精准的定位。
但仍然有一些问题,主要体现在异常上下文信息容易缺失。在 Android 端表现为异常被 try-catch 了,iOS 端则表现为向 nil 发消息,两端的相似之处是当前位置确实不会崩溃,但是后面的流程对不对就不知道了。对于可能发生异常的地方,不崩溃不仅意味着对当前业务场景有兜底方案,而且后面的流程也要是正常的,同时将这次的兜底行为上报,这样才算比较完善的做好了事中记录。比如初始化播放器失败,不仅要保证失败情况下对任何业务不会产生崩溃行为,也要将这次失败结果上报,以便更好的监控异常情况,比用户更早感知到问题。
事后止损
承认做不到百分百没有崩溃,因此我们得时刻去监控它。
如果崩溃行为造成的后果很严重,得有相应的止损策略,事后止损 = 监控 + 止损。
止损策略有:
- 功能开关 — 应用内灰度
- 热修复
- 降级
- 动态化
其中「动态化」是带有部分止损的功能,但它本身并不是专门的止损策略。
举一个架构和止损策略相结合的例子,业务模块之间的通信在我们的架构上都会走到一个统一的路由模块去分发,如果我们能够发现一个崩溃出现在了某个业务里,那么我们就可以下发指令到我们的路由模块,拦截掉那个业务的请求,并跳转到其他的页面,如果那个业务恰好很重要,是核心流程,那我们就跳转到具备同样功能的 H5 页面上,搭配降级保证核心链路的可用性。
这个止损的例子,就是架构能力+止损策略的体现,你没有好的架构,可能就没办法完成这个止损。那么只能眼睁睁的看着崩溃发生,从增量变成存量而无能为力。
由点到面
修复一个崩溃问题时,不能简单的只处理这个问题,要考虑在其他地方是否也有类似的潜在崩溃,如果有的话,要做统一的处理和预防。