本文同步发布于公众号:移动开发那些事: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的问题,对问题进行分类治理,可以有效提升应用的稳定性。

Android 稳定性(二):治理思路篇的更多相关文章

  1. 【转】Android LCD(二):LCD常用接口原理篇

    关键词:android LCD TFT TTL(RGB)  LVDS  EDP MIPI  TTL-LVDS  TTL-EDP 平台信息:内核:linux2.6/linux3.0系统:android/ ...

  2. Android LCD(二):LCD常用接口原理篇(转)

    源: Android LCD(二):LCD常用接口原理篇

  3. Android插件化技术——原理篇

    <Android插件化技术——原理篇>     转载:https://mp.weixin.qq.com/s/Uwr6Rimc7Gpnq4wMFZSAag?utm_source=androi ...

  4. Android Fragment使用(一) 基础篇 温故知新

    Fragment使用的基本知识点总结, 包括Fragment的添加, 参数传递和通信, 生命周期和各种操作. Fragment使用基础 Fragment添加 方法一: 布局里的标签 标识符: tag, ...

  5. 直接拿来用!最火的Android开源项目(完结篇)

    直接拿来用!最火的Android开源项目(完结篇) 2014-01-06 19:59 4785人阅读 评论(1) 收藏 举报 分类: android 高手进阶教程(100) 摘要:截至目前,在GitH ...

  6. 【转】android camera(二):摄像头工作原理、s5PV310 摄像头接口(CAMIF)

    关键词:android  camera CMM 模组 camera参数  CAMIF平台信息:内核:linux系统:android 平台:S5PV310(samsung exynos 4210) 作者 ...

  7. Android自定义控件系列之应用篇——圆形进度条

    一.概述 在上一篇博文中,我们给大家介绍了Android自定义控件系列的基础篇.链接:http://www.cnblogs.com/jerehedu/p/4360066.html 这一篇博文中,我们将 ...

  8. Android 异步消息处理机制终结篇 :深入理解 Looper、Handler、Message、MessageQueue四者关系

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.概述 我们知道更新UI操作我们需要在UI线程中操作,如果在子线程中更新UI会发生异常可能导致崩溃,但是在UI线程中进行耗时操作又会导致ANR,这 ...

  9. 打开Voice Over时,CATextLayer的string对象兼容NSString和NSAttributedString导致的Crash(二解决思路3)

    续前一篇:打开Voice Over时,CATextLayer的string对象兼容NSString和NSAttributedString导致的Crash(二解决思路2)ok,到这里已经能够锁定范围了, ...

  10. Android进阶(二十八)上下文菜单ContextMenu使用案例

    上下文菜单ContextMenu使用案例 前言 回顾之前的应用程序,发现之前创建的选项菜单无法显示了.按照正常逻辑来说,左图中在"商品信息"一栏中应该存在选项菜单,用户可进行分享等 ...

随机推荐

  1. C语言(从入门到入土)

    1.C语言整体上需要记住的 总体上必须清楚的: 1)程序结构是三种: 顺序结构 , 循环结构 (三个循环结构), 选择结构 (if 和 switch) 2)读程序都要从main()入口, 然后从最上面 ...

  2. linux不常用命令

    1.查看进程的内存资源占用 [root@abdi1 elasticsearch]# pidstat -r -p 1791 1 Linux 3.10.0-514.el7.x86_64 (abdi1) 0 ...

  3. Java EasyExcel 导出报内存溢出如何解决

    大家好,我是 V 哥.使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时.你有遇到过这种情况吗,以下是V 哥整理的解决该问题的一些常见方法,分享给大家,欢迎一起讨论 ...

  4. Nuxt.js 应用中的 nitro:config 事件钩子详解

    title: Nuxt.js 应用中的 nitro:config 事件钩子详解 date: 2024/11/2 updated: 2024/11/2 author: cmdragon excerpt: ...

  5. 还在为慢速数据传输苦恼?Linux 零拷贝技术来帮你!

    前言 程序员的终极追求是什么?当系统流量大增,用户体验却丝滑依旧?没错!然而,在大量文件传输.数据传递的场景中,传统的"数据搬运"却拖慢了性能.为了解决这一痛点,Linux 推出了 ...

  6. Flink Time

    基础概念 支持三种时间概念: Processing Time 时间递增 Ingestion Time : 摄入时间,数据进入Flink框架的时间,在Source Operator中设置,每个事件拿到当 ...

  7. JAVA并发编程学习笔记之Unsafe类

    java不能直接访问操作系统底层,而是通过本地方法来访问.Unsafe类提供了硬件级别的原子操作,主要提供了以下功能: 1.通过Unsafe类可以分配内存,可以释放内存: 类中提供的3个本地方法all ...

  8. The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online (The 2nd Universal Cup

    The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online (The 2nd Universal Cup. Stage 1: Qingdao) J ...

  9. 【朝花夕拾】蓝牙&WiFi常识篇

    一.蓝牙常识点 1.常见英文缩写 缩写 英文全称 释义 BLE Bluetooth Low Energy 低功耗蓝牙 BR Basic Rate 基本速率,一般说的经典蓝牙就是指BR/EDR EDR ...

  10. Spring AOP基础、快速入门

    介绍 AOP,面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理.日志.缓存.权限控制.限流等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度.切面就是那些与业务 ...