Android 稳定性优化

保证程序的稳定可以从内存、代码质量、Crash、ANR、后台存活等知识点来展开优化。

代码质量

  • 团队之前相互代码审查,保证了代码的质量,也可以学习到了其它同事码代码的思想。
  • 使用 Lint 扫描代码,查看是否有缺陷性。

Crash

  • 通过实现 Thread.UncaughtExceptionHandler 接口来全局监控异常状态,发生 Crash 及时上传日志给后台,并且及时通过插件包修复。
  • Native 线上通过 Bugly 框架实时监控程序异常状况,线下局域网使用 Google 开源的 breakpad 框架。发生异常就搜集日志上传服务器 (这里要注意的是日志上传的性能问题,后面省电模块会说明)

ANR

概念:

  • 没有在规定的时间内,干完要干的事情,就会发生 ANR。
  • Activity / 输入事件:5s
  • BroadcastReceiver、Provider:10s
  • Service : 20s

产生 ANR 原因:

  • 主线程堵塞,死循环
  • 死锁
  • 频繁大量 GC
  • 当前应用程序抢占 CPU 时间片失败

ANR 分析:

  • ANR in : 发生 ANR 的具体类
  • PID: 发生 ANR 的进程,系统在此时会生成 trace 文件
  • Reason:当前 ANR 类型以及导致 ANR 的原因
  • CPU usage: CPU 使用情况,在日志中 CPU usage 有两个时间点,第一个是发生 ANR 前的 CPU 使用情况,第二个是发生 ANR CPU 的使用情况
    • 从 LogCat 中除了能看出在哪个类发生 ANR 以及 ANR 的类型,具体的原因主要还是看 CPU 的使用情况,如果 CPU 使用量很少,说明主线程可能阻塞,如果 IO wait 很高,说明 ANR 有可能是由于主线程进行耗时的 I/O 操作造成的。
    • LogCat 分析 ANR 流程
      • 抓取 bugreport,搜索 ANR in,查看发生的时间和进程,Cpu 的负载有没有问题
      • 根据进程寻找主线程的 trace,发现被 blocked 的地方,如果是 Binder call 则,进一步确认下对端的情况;如果是耗时操作,直接修改成异步,怀疑系统执行慢可以看看 binder_sample,dvm_lock 等信息,其次 gc 多不多,lmk 杀进程是不是很频繁,都可以看出系统的健康状态。
      • 结合源码进行分析解决
  • 通过 adb pull /data/anr/traces.txt 可以拿到然后通过 AS - Analyze ->Analyze Stacktrace 分析。如果某个线程报红说明被堵塞了。

ANR 监控:

原理:ANR 监控原理和卡顿监控原理类似都是通过 Looper 的 Printer 来实现。

后台存活

进程生命周期:

  • 前台进程:正在交互,只有在内存不足,系统才会终止它们。
  • 可见进程:没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程,可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
  • 服务进程:正在运行已使用 startService () 方法启动的服务且不属于上述两个更高类别进程的进程。除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
  • 后台进程:对用户不可见的 Activity 的进程,系统可能随时终止它们。
  • 空进程:不含任何活动应用组件的进程,最容易被杀死。

LMK 杀进程标准: oom_adj 值越低越不容易被回收:查看 oom_adj 值

  • adb shell ps 拿到查看进程的 pid 值
  • cat /proc/[pid]/oom_adj

优化方案:

  • Activity 提权:监控手机锁屏解锁事件,在屏幕锁屏时启动 1 个像素透明的 Activity ,在用户解锁时将 Activity 销毁掉,从而达到提高进程优先级的作用。
  • Service 提权 :SDK >= 26 通过 startForegroundService 启动一个前台服务,如果开启 startForegroundService 前台服务,那么必须在 5 s 内开启一个前台进程的服务通知栏,不然会报 ANR
  • 广播拉活:在发生特定系统事件时,系统会发出广播,通过在 AndroidManifest 中静态注册对应的广播监听器,即可在发生响应事件时拉活。但是从 android 7.0 开始,对广播进行了限制,而且在 8.0 更加严格。
  • 全家桶拉活
  • Service 机制拉活。将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活
    只要 targetSdkVersion 不小于 5,就默认是 START_STICKY。
    但是某些 ROM 系统不会拉活。并且经过测试,Service 第一次被异常杀死后很快被重启,第二次会比第一次慢,第三次又会比前一次慢,一旦在短时间内 Service 被杀死 4-5 次,则系统不再拉起。
  • 账号同步拉活(只做了解,不靠谱)
  • JobScheduler 拉活 (靠谱,8.0 官方推荐)。JobScheduler 允许在特定状态与特定时间间隔周期执行任务。可以利用它的这个特点完成保活的功能,效果即开启一个定时器,与普通定时器不同的是其调度由系统完成。
    注意 setPeriodic 方法
    在 7.0 以上如果设置小于 15 min 不起作用,可以使用 setMinimumLatency 设置延时启动,并且轮询
  • 推送拉活:根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送。
  • Native 拉活:Native fork 子进程用于观察当前 app 主进程的存亡状态。对于 5.0 以上成功率极低。
  • 后台循环播放一条无声文件。耗电
  • 双进程守护 (靠谱)
  • 加入白名单电量优化
  • 总结: Activity + Service 提权 + Service 机制拉活 + JobScheduler 定时检测进程是否运行 + 后台播放无声文件 + 双进程守护可以组成一个进程保活终极方案。

备注

参考资料:

单词音标: