原创 郑丹 2024-10-25 08:31 浙江
饿了么商家版APP通过专项治理行动,成功将NativeCrash率从万6降至十万分之六,显著提升了用户体验和软件稳定性。
这是2024年的第81篇文章
( 本文阅读时间:15分钟 )
01
背景
在App开发过程中,崩溃率是衡量App稳定性的关键指标。因为App崩溃不仅仅影响用户的即时体验,更对用户留存率构成了潜在的威胁。它如同一颗隐形的定时炸弹,随时可能引发用户体验的灾难。App崩溃分为Java Crash和Native Crash 2种。
Native Crash因为其上下文信息不完整、错误提示模糊以及难以捕捉等特性,相比较Java Crash,定位及修复更为棘手困难。作为外卖商家日常运营不可或缺的助手,饿了么商家版APP的用户体验一直是我们团队的首要考量。然而,伴随着业务迭代与设备系统的不断升级,我们的应用Native Crash率上升明显,一度从原先的万2攀升到了万6以上。为此我们启动了Native Crash的专项治理行动,致力于为用户提供一个更加可靠稳定及高效的App应用,让所有商家都能享受到无忧的数字化运营体验。
(治理前crash趋势表)
02
Native Crash 成因及描述说明
为了更好的治理Native Crash,先来了解下Native Crash 的成因。从上图可以看到,发生在Java层的Crash称为Java Crash,该Crash异常日志有具体的堆栈信息可以查看分析,修复相对明确具体。发生在Native层的Crash称为 Native Crash,由于大多该类型crash堆栈信息模糊,导致定位耗时,成效低。
常见导致Native Crash的原因有如下几种类型:
1. jni内部数组越界、缓冲区溢出、空指针、野指针等;
2. jni中多线程出现竞争,比如一个线程调用jni接口释放了内部一个指针,另一个线程调用另外一个jni接口还在使用这个指针;
3. Android ART发现或出现异常;
4. 其他framework、Kernel或硬件bug。
但是在Native Crash上报异常日志中均会包含异常的信号量及信号Code码,故了解信号量及信号Code有助于我们加快确定排查方向。
信号量 | 信号Code | 描述 |
SIGSEGV | SEGV_MAPERR | 地址不在 /proc/self/maps 映射中,很可能是JNI中空指针的问题 |
SEGV_ACCERR | 没有访问权限 | |
SEGV_MTESERR | MTE特有类型 | |
SIGABRT | 程序主动退出,常见调用函数abort(),raise()等 | |
SIGILL | ILL_ILLOPC | 非法操作码(opcode) |
ILL_ILLOPN | 非法操作数(operand) | |
ILL_ILLADR | 非法寻址 | |
ILL_ILLTRP | 非法trap,如_builtintrap()主动崩溃 | |
ILL_PRVOPC | 非法特权操作码(privileged opcode) | |
ILL_PRVREG | 非法特权寄存器(privileged register) | |
ILL_COPROC | 协处理器错误 | |
ILL_BADSTK | 内部堆栈错误 | |
SIGBUS | BUS_ADRALN | 访问地址未对齐 |
BUS_ADRERR | 访问不存在的物理地址 | |
BUS_OBJERR | 特定对象的硬件错误 | |
SIGFPE | FPE_INTDIV | 整数除以0 |
FPE_INTOVF | 整数溢出 | |
FPE_FLTDIV | 浮点数除以0 | |
FPE_FLTOVF | 浮点数上溢(overflow) | |
FPE_FLTUND | 浮点数下溢(underflow) | |
FPE_FLTRES | 浮点数结果不精确 | |
FPE_FLTINV | 无效的浮点运算 | |
FPE_FLTSUB | 越界 |
(异常描述表)
03
准备工作
当前我们使用的crash收集平台和阿里集团一致,都使用EMAS平台来收集和分析应用的 Crash 异常堆栈信息,该平台功能和市面上常用的友盟、bugly等应用监控平台功能类似,提供crash数量、关键异常信息及异常堆栈内容等。同时结合UC LogAnalyzer 分析工具(主要作用分析UC webview相关异常) 来协助排查问题提升处理效率。
04
APP异常整体分析
为了更好的分析 Native Crash,我们以2024年度某个时间点top crash聚合数据为例来展开分析。
针对上述top crash特征分析提取内容如下:
crash类型 | 数量 | 设备特征 |
libart.so | 1858 | |
libart.so (_ZN3artL38Executable_getMethodReturnTypeInternalEP7_JNIEnvP8_jobject+52) | 1475 | |
libjsi.so | 1409 | |
ligGLES_mali.so | 2081 |
05
案例Case说明
5.1 libart.so 异常
在分析top1 libart.so问题时,解析相关日志发现问题大多发生在APP初始化阶段。以某个crash日志为例,发生问题的进程id=24657,线程id=17891。
查看日志中与线程活动相关的记录,注意到tag为“XMPush-24657”的异常日志,初步判断可能为小米推送服务初始化异常导致。从前面该异常特征分析看99%以上都集中在小米和红米设备上,和小米推送刚好能关联上。但是分析小米推送jar包代码及和集团多个采用相同版本小米推送服务的团队交流确认,他们并未遇到类似问题。和小米技术同学对接,对方也未接收到该版本异常反馈。故先暂时否定小米推送引发异常问题推论。
为了获取更多信息,只能再次分析日志。捞取数十份日志后,在某个日志中发现发生异常时主线程信息。
经深入探究,发现App启动过程中某个特定场景需要在初始化阶段启动一个独立线程对SharePreferences文件处理后存储操作,而该处理过程存在潜在风险,可能诱发不可预期的崩溃问题。因此尝试在灰度版本改用文件来处理。新版本上线后该crash清零。
5.2 libjsi.so 异常
从日志上看crash信号量为 SIGSEGV,code = SEGV_MAPPER 。从前面章节的异常描述表中可知 SEGV_MAPPER 异常为空指针异常。翻看日志找到发生异常的调用链堆栈如下。
jsValue = callbackFunction.call(jsContext, null, returnJSValue);
从上面代码分析如果异常确实是空指针造成,那么就有2种可能:
外部传入的参数为空;
so c++代码变量为空。
其中 异常的 libjsi.so 由外部三方提供,遵循优先排查自身可控因素的原则,我们先来排查调用方法的三个参数,jsContext 成了引发问题的最大疑点。为了验证这一假设,我们将 jsContext = null 强制赋值作为参数进行验证,复现了该异常,其中异常堆栈和错误信息与用户上报的内容吻合。确定了原因后通过对参数判空来解决。
// 如果被回收了就不执行
if (jsContext != null && jsContext.isDisposed()) {
return null;
}
jsValue = callbackFunction.call(jsContext, null, returnJSValue);
5.3 arm-v7a OOM 异常
上述日志显示在分配内存时发生异常 ( Adreno-GSL: <gsl_memory_alloc_pure:2955>: ERROR: kgsl_sharedmem_alloc() failed! Allocation size: (10200 KB); Flags: (0x100000) ),out of memory 了。同时结合查看发生异常的设备特征均为32位so (CPU型号: armeabi-v7a)
推断低版本设备在申请分配内存时异常,参考《阿里开源 Patrons:大型 32 位 Android 应用稳定性提升 50% 的“黑科技”》文章引入 Patrons 库来解决提升低端设备性能。
5.4 #00 pc 0xda58 [anon:.bss]
异常报错不局限于上述的 anon:.bss,也可能是 libwebviewuc.so 或者关联了其他 so 报错。但是日志中有下面特征:
app启动时间较短,一般都在几秒内;
相关线程名为 ANRHandler ,并且日志中对应 tag: ANRHandler 条目中包含类似 app=cn.chuci.and.wkfenshen 等对应多开包名信息。
或者搜索异常日志,可以发现[anon:.bss]日志下真实包名前面为多开软件的包名信息:
根据上述 app 包名查询了解到我们应用的启动源于多开软件的使用,通过回访部分用户确认是在该时间点通过多开软件打开了相关的app,但是用户并未感知到崩溃异常。初步判断,在多开环境下,应用在初始化阶段可能遭遇了某些异常情况,导致崩溃并上报异常。同时多开环境自动重启应用,从而使得用户未感知到崩溃现象,只觉得是打开变慢了。
针对多开异常,我们考虑两种解决方案思路:
一是app初始化时检测是否处于多开环境,如果是则限制应用初始化。如何检测线上已有对应方式;
二是将多开环境下的崩溃问题作为普通崩溃异常解决。从用户体验和业务安全的角度考虑,允许用户合理多开是更优的选择。
然而,鉴于多开环境的复杂性和多样性,全面解决这一问题将需要投入大量的人力资源。鉴于当前此类崩溃事件数量较少,占比较低(1%以下),我们的策略是暂时保持观察状态,持续监控该异常情况,以便在必要时采取进一步行动。
06
结果
最终通过团队成员的通力合作,我们成功地将App的Native Crash率优化至历史低位——仅0.007%。这个结果不仅提升了app的稳定性,更获得了用户的一致好评与积极反馈。我们后续将持续关注并进一步优化产品性能,为用户提供更加稳定、流畅的应用体验。
(治理后crash趋势表)
参考链接
[01] 《阿里开源 Patrons:大型 32 位 Android 应用稳定性提升 50% 的“黑科技”》
https://www.infoq.cn/article/bvbf3iwjztvem4szamvw
[02] 《android多开原理和检测》
https://blog.csdn.net/c_kongfei/article/details/119914810
欢迎留言一起参与讨论~