Android 稳定性(二):治理思路篇

本文同步发布于公众号:移动开发那些事:Android 稳定性(二):治理思路篇

一般来讲Android稳定性包括crashANR,本文主要围绕crash(应用的crash率)来讲述如何来做Android的稳定性相关的工作。在讲具体的思路之前,我们先来了解一下Android的异常捕获机制

1 异常捕获机制

Android中的异常捕获机制从语言层面可以划分为java层和native(C++)层。

1.1 java异常捕获机制

1.1.1 基础

Throwable是所有异常的基类,它有两个重要的子类:

  • Error : 严重的系统错误,如OOM,一般应用程序没有办法对其进行处理
  • Exception:可被应用程序捕获和处理的异常,如NPE

一般我们在代码里处理的都是Exception相关的异常,而这些异常里,根据是否需要编译阶段处理,划分为两类:

  • 受检异常: 编译阶段就需要处理的,否则代码就无法通过编译(一般通过try-catch或者方法签名中使用throws声明会抛出该异常),如文件操作时的IOException;
  • 非受检异常:编译阶段不要求处理,但运行时会出现的异常,包括运行时异常RuntimeException及其子类

1.1.2 使用

除了代码中很常用的,通过try-catch块来进行包裹可能出现异常的问题代码块外,还可以通过 Thread.setDefaultUncaughtExceptionHandler 对应用全局的异常进行捕获。例如在 Application 类中设置此方法,当发生未处理的异常时,能够将异常信息记录下来,方便后续分析。一般是通过自定义类来实现UncaughtExceptionHandler的接口来实现全局的异常处理:

复制代码
class ACrash implements Thread.UncaughtExceptionHandler {
	public UncaughtExceptionHandler exceptionHandler;
	@Override
	public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
       // 这里根据异常的类型和线程来做自定义的处理    

       // 在处理完自定义逻辑后,判断是否要把异常继续给原来的异常处理器
       if (exceptionHandler != null) {
       	exceptionHandler.uncaughtException(t, e) 
       } 

    }
}

这里在设置自定义异常处理接口时,有个点需要注意的是,如果有使用第三方的的crash收集系统,像bugly,acrc,此时在设置异常处理器时,需要注意是否已经设置过了:

复制代码
UncaughtExceptionHandler tmpHandler = Thread.getDefaultUncaughtExceptionHandler()
ACrash aHandle = ACrash()
// 要保留原来的异常处理
aHandle.exceptionHandler = tmpHandler
Thread.setDefaultUncaughtExceptionHandler(aHandle)

1.2 native 异常捕获机制

1.2.1 基础

native 层的异常捕获机制,除了类似的try-catchthrow抛出异常外,还有个系统层的信号分发处理机制。系统会通过分发信号来告知异常信息,所以异常的处理就是一个信号的处理,

一般可通过sigaction函数来注册信息处理函数:

复制代码
static void signalHandler(int signal, siginfo_t *info, void *reserved) {
    // 处理信号
}
void initSignalHandler() {
    struct sigaction action;
    action.sa_flags = SA_SIGINFO;
    action.sa_sigaction = signalHandler;
    sigaction(SIGSEGV, &action, NULL); // 捕获段错误
}

常见的信号量:

  • SIGSEGV 11 无效的内存引用
  • SIGABRT 6 由abort发出的退出指令
  • SIGFPE 8 C浮点异常
  • SIGILL 4 非法指令
  • SIGBUS 10,7,总线错误(内存错误)
  • SIGKILL 9 kill 信号

1.2.2 使用

native 异常捕获后,还涉及到minidump文件的获取和整个堆栈的还原,比较复杂,所以一般我们不直接自己注册信号监听来处理,会使用第三方的解决方案,如bugly,或使用breakpad 库来处理(bugly底层也是使用的breakpad),breakpad的使用可参考:如何在Android平台使用Google Breakpad

2 分类与治理思路

2.1 分类

除了常规的业务代码的优化外(像常规的npe,indexoutofboundsexception等)还可以从操作系统的角度,将稳定性的优化问题可以大概分为以下几类:

  • 内存稳定性优化
  • 线程稳定性优化
  • 系统问题优化

2.2 治理思路

稳定性治理其实最终是为用户服务的,因此整个治理也是围绕提高用户的体验来展开。治理的思路主要是:

  • 能修复的,尽量去修复(像npe,oom);
  • 从业务上没有办法修复(像系统bug),则尽可能减少对用户的影响,对一些异常进行降级

提高用户的体验,无非就是要降低应用的crash率,这里有个核心的原则是:主要精力要花在Top 10Top20的问题分析上,把主要的问题解决,顺带解决一些长尾的问题;

2.2.1 内存治理

内存问题的治理主要围绕:尽可能减少运行的内存占用同时避免出现内存泄露,内存溢出的问题。这里需要借助一些工具来辅助我们判断可能的问题是什么:

  • leakcanary: Square开源的,检测和诊断 Android 应用中的内存泄漏的工具,用于开发过程;
  • KOOM : 快手出的线上内存监控方案,可帮助更好优化应用的内存
  • Profiler:Android 自带的性能监控工具,可辅助分析内存
    (业内也还有其他的用于内存分析的工具,可以自行选择合适的,能解决问题就行)

一些优化内存的方式,可参考前面的文章Android稳定性(一):内存使用指南

2.2.2 线程治理

线程治理主要围绕:

  • 线程复用,尽可能使用线程池的方式来调度(不同的业务会采用不同的线程池策略);
  • 线程回收,线程使用完毕后,要及时调用关闭(shutdown),如果有持有线程的变量,也要及时置空

这里笔者在业务中,有尝试过几个优化的方向:

  • 限制OkHttpClient的最大线程数,避免无限增长,并且尽可能复用同一个OKHttpClient
  • 收敛线程,提供几个从线程池获取线程的方法,避免业务直接new
  • 线程池初始化的core线程根据不同的业务做差异化的初始化;
  • 避免线程的实例被某个单例持有,导致线程关闭后也没有办法释放资源;

2.2.3 系统问题治理

由于Android版本的碎片化问题,会遇到各种只收集到系统堆栈的crash问题,无法从业务层面解决,这里就只能具体问题具体分析。针对系统问题,有个大的治理(分析)思路:对发生问题的系统版本进行聚类,判断是特定版本的问题还是通用的问题,有个猜测后,再去分析对应版本的源码验证猜测(假设 -> 确定问题->解决问题)

可在线查看Android源码的地址:Android Code Search

在确定问题后,在思考如何解决问题(系统问题大概率无法根治,只能尽可能减小对用户的影响)时,这里也有个大的框架:

  • 能通过hook系统接口处理的,就通过hook系统接口来处理(一般需要在C++层进行hook),如:
    • 扩大系统的限制,如Android 8.1系统的文件描述符限制为1024,可通过hook接口扩充到4096,可减小由于fd溢出引起的问题;
    • 降低系统的级别影响,如将RenderThread的问题由abart的 crash降低到丟帧;
  • 没有办法通过hook系统处理的:
    • 不影响用户的异常,如DeadSystemException,FinalizerWatchdogDaemonTimeout这一类的,则可以直接在业务层catch住(可参考前面的异常捕获机制)
    • 影响用户体验的异常,则还是要走crash的逻辑

3 总结

本文围绕Android应用的crash率阐述了Android稳定性相关工作,先介绍异常捕获机制,再提出分类与治理的思路,旨在降低应用 crash 率,提升用户体验。

通过合理的异常捕获机制的设置和优化策略,重点关注应用Top10和Top20的问题,对问题进行分类治理,可以有效提升应用的稳定性。

相关推荐
每次的天空1 小时前
Android学习总结之算法篇五(字符串)
android·学习·算法
Gracker2 小时前
Android Weekly #202513
android
张拭心4 小时前
工作九年程序员的三月小结
android·前端
每次的天空4 小时前
Flutter学习总结之Android渲染对比
android·学习·flutter
鸿蒙布道师6 小时前
鸿蒙NEXT开发土司工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
智想天开7 小时前
11.使用依赖注入容器实现松耦合
android
yunteng5218 小时前
音视频(四)android编译
android·ffmpeg·音视频·x264·x265
tangweiguo030519878 小时前
(kotlin) Android 13 高版本 图片选择、显示与裁剪功能实现
android·开发语言·kotlin
匹马夕阳8 小时前
(一)前端程序员转安卓开发分析和规划建议
android·前端
Kika写代码8 小时前
【Android】UI开发:XML布局与Jetpack Compose的全面对比指南
android·xml·ui