崩溃率从1%到0.02%,iOS稳定性解决之道

2020-12-21  本文已影响0人  宋奕Ekis

在《移动互联网技术质量体系的理解》中,提到了在团队中以数字衡量技术质量的体系建设。其中,iOS在今年双周迭代的情况下,两个项目都保持了一个季度崩溃率稳定万分之二的水准,而这个数字在两年前是1%以上。

本周在外部沙龙上做了相关工作的总结,enjoy~


相信大家都一样,在工作中做了非常多的工作,无论是业务上的还是技术上的。但是我们如何去衡量,去评判我们所做工作的好坏呢?作为技术人员,如何去量化我们为用户提供服务的优劣呢?我们需要一个相关性指标来衡量在线状态。

就像每个月末的KPI考核一样,在线上环境中设立了很多指标性数据,例如:

Image

其中,客户端最关注崩溃率卡顿率。崩溃率的定义如下:

崩溃率 = 每天崩溃的用户数 / 每天使用APP的用户数

考虑到崩溃对用户带来的体验极差,很可能导致用户流失,所以我们计算了一下,每崩溃一个人用户我们所损失的直接利益:

如下图,听云提供了一个基准,千分之三为标准和优秀的分水岭。

Image

虽然从标准上来看,千分之三已经是个优秀的值了。但是对于大体量的APP来说,上面所推算的成本就足以说明,千分之三远远不够。

我们自己定制了一个数据红线,不能连续七天超过万分之五。每天1万个人使用,崩溃人数不可超过5人。即使出现任何情况超过了此阈值,也要在七天内想尽各种方案解决问题,要保证这项指标的稳定。

用户第一是我们最重要的一条价值观,我们抱着不能失去任何一个的态度在进行我们的工作。当然,这个目标和标准也不是一蹴而就的。我们这个指标从2017年初开始建立,经过不断的探底深入,最终形成了现在一个稳定的阈值。

Image

有了一个目标,那如何检测和收集线上数据呢?

  1. 开源的,可以自己搭建平台
  1. 现有的服务
Image

我们选择是腾讯的bugly,有以下几点原因:

Image Image

在解决这些问题的路上,我们总结出了一些方法和经验。

架构本身以及线程乱用所导致的Crash


遇到过很多奇怪的崩溃信息,例如

在起初的修复中,只是分别治理,遇到一个地方治理一个地方,尤其是因为线程混乱导致的问题,改起来真的很头痛。

在经历了一段时间这种“有问题处理问题”临时解决方案的日子后,终于下定决心面对老工程。整理问题并分析这些问题发生的原因:网络层和数据解析层的隐患严重,其中主要由于线程混乱,服务器脏数据,以及数据未格式化彻底直接抛给业务层,导致业务层模块重,数据混乱导致的各种Crash。比较典型的是

在重构之后,代码层级改为更加清晰的结构。其中,业务层只关心业务代码,发送请求。

  1. 网络层负责,格式化请求数据,并组织管理网络请求的生命周期。
  1. 数据解析层,负责Json转model,以及格式化服务器数据的工作。

此层级重点解决的是服务器数据中存在脏数据,服务器数据结构变化导致的各种问题,业务层接到的数据,是完全“干净的”数据,可以放心使用。

Image

缓存层的重构


重点解决线程混乱,导致脏数据,以及效率过低导致的卡顿问题。

Image

在针对业务分析之后,分别尝试了WCDB和LevelDB作为底层数据库。它们都支持多线程并发读写,对于所有数据的前期格式化新开辟一个线程进行,不占用主线程资源。

最终的读取和存储都回到主线程中进行,保证存储和读写与UI保持线程和时序的一致。

经过以上的架构分析和重构,我们解决了大约60%的Crash。

Image

制作工具类


崩溃多种多样,除了一个好的架构之外,很多事情都可以使用工具或者良好的封装来一次性避免。例如,使用NSObject+YGSafeAddition.h来避免NSArray和NSDictionary常见的一些问题。参考代码如下:

Image

网上有很多使用MethodSwizzling替换NSArray方法的,经过线上实践验证,在iOS9系统上,系统键盘弹出时将应用切换到后台,导致替换方法崩溃,并且偶现。系统方法内部的实现会有很多意想不到的耦合情况,所以建议大家,还是老老实实写一个category,不要在工程中使用系统方法了。

面对常见的KVO错误,我们选择使用Facebook的KVOController来解决问题。避免过度释放的问题。

代码规范


Image Image

第三方SDK导致的问题


我们为了业务接入了各种第三方SDK,其中尤其是闭源的SDK中产生的崩溃最令人头痛。此时我们可以根据crash堆栈,以及用户行为(bugly看到经过的页面,用户行为日志看到调用的接口),来尽量分析还原用户崩溃时所在的场景。

如果可以通过自己解决的,先行解决,并知会相关第三方修复。如果是升级SDK导致的,我们需要衡量业务的情况下,尽量回退到稳定版本。而在引入一个新的第三方SDK时,一定要注意是否和其他第三方SDK存在冲突,无论是编译上的还是运行时。项目中曾遇到七鱼和gRPC在常量定义上的冲突,神策和有赞在UA定义上的冲突。

Image

一些难缠的问题


这个问题在去年应该只要是iOS开发者应该都听过,xcode10打包的APP在iOS9上全面崩溃。和keep沟通,他们的选择是放弃了iOS9用户。。。相信很多同学也在网上搜索了很多方法,例如说是p3图片在Assets中导致,但是并没有起作用。最终发现,只要切换回旧编译器就好了。

主要是因为APP进入后台以后,UIWebView继续使用openGL渲染,只要一帧就崩溃。目前我们的工程内部已经全局替换成了WKWebView,可以规避此类问题。并且Apple在接下来,也会彻底抛弃UIWebView。

此问题上面已经说过,需要规定我们的代码规范去解决。而导致此问题的原因主要有二:

  • 声明变量的时候,应该使用copy或者strong时,却错误的使用了weak或者assign,导致其在该持续持有此对象时,此对象已经被系统提前释放。
  • 使用CF开头声明的,或者使用了其他底层对象,其内存管理并不归ARC管理,所以需要手动管理,这个时候如果忘记使用CFRelease等响应方法释放,就会导致对象被提前释放的情形

当时遇到此问题时,只觉得发生场景不定,发生时机不定,整体来说看不出来到底是哪里出现了问题。整体梳理了一遍工程代码之后,才发现很多老代码根本不够规范,变量声明随意,或者复制粘贴导致的错误。所以最终,将此问题重点列入了我们的代码规范和发版规范中。

上线之前一定要全局搜索property定义,避免属性定义错误的情况。很明显的一个就是对象定义成weak。如果不是特定情况,例如为了避免循环引用特别注明的,那么对象的声明都应该是strong。避免过度释放产生的object_release类型crash。

Image

很多让人摸不着头脑的问题,经过仔细的分析,尤其是针对用户所在页面或者所处场景的还原,都可以稳定复现,其中:

等等一系列信息,都可以帮助我们分析解决问题。

提前发现和快速处理


调试问题和解决崩溃时,一个很重要的能力,即调试别人代码的能力,无论是开源的,还是闭源使用LLDB调试的。已经处理了这么多问题,但是随着业务增长,系统更新,新的问题必然会不断产生。如何保证在未知的情况下,继续保证线上的稳定呢?

我们主要做了以下两件事情:

Image

总体来看,稳定性并非一蹴而就,从1%到0.02%,两个数量级的降低背后是上述的种种努力,将问题回归、整理,形成技术沉淀、适合团队工作方式、匹配产品业务的一套方法论。我们会继续将稳定的工作纳入到每天的日常工作中,为用户价值持续努力。

上一篇下一篇

猜你喜欢

热点阅读