从 ApplicationExitInfo 看 Android 应用的退出类型

1,862 阅读4分钟

Android 系统都可能因为哪些原因而杀死应用?

之前有次和朋友聊起这个问题,第一反应就是 Low Memory Killer 在整机物理内存紧张时杀进程的情况,是否有其他情况就说不上来了。

实际上,长久以来,移动终端上( 无论 Android 还是 iOS )完整感知应用的退出情况都是一个难题。

对于 iOS 来说,Facebook 很早之前就提出了通过穷举应用自身可感知的退出类型,通过排除法来检测 OOM Crash 的方法;对于 Android 来说,用户手动退出以及系统因为资源紧张而杀死应用的各类情况,应用自身都是无法实时感知的。好在 Android 11 之后,系统引入了 ActivityManager.getHistoricalProcessExitReasons 方法,我们得以在事后完整获取到应用的退出情况。

这里我们基于 Android 11 新引入的 ApplicationExitInfo,来具体看下 Android 应用都有哪些情况会退出。

应用可实时感知类型

应用自杀,Crash 以及 ANR 都属于应用可以实时监听的退出类型,对应到 ApplicationExitInfo 枚举的应用退出类型主要是 REASON_EXIT_SELF,REASON_CRASH / REASON_CRASH_NATIVE 以及 REASON_ANR。

  • REASON_EXIT_SELF 实际对应的是应用通过 System.exit 退出的情况。Zygote 进程通过监听 SIGCHLD 信号来感知子进程( 即应用进程 )exit,并通过 Unix Domain Socket “/data/system/unsolzygotesocket” 将应用退出信息通知给 AMS
  • REASON_CRASH / REASON_CRASH_NATIVE,对应 Java Crash 和 Native Crash。Java Crash 会通过应用启动之初注册的 KillApplicationHandler( 即 UncaughtExceptionHandler )通知给 AMS;至于 Native Crash,在监听信号将 Native Crash 写入 tombstone 的流程中会通过 Unix Domain Socket “/data/system/ndebugsocket” 通知给 AMS
  • REASON_ANR,对应 ANR,ANR 原本就是通过 AMS 自身的 WatchDog 机制实现的,AMS 通过内部逻辑打点来区分这一退出类型

注意到应用通过 Process.killProcess 自杀的情况,实际不会被归类到 REASON_EXIT_SELF 中。killProcess 实际是通过发送 signal 9 来实现的,和一众难以归类的系统杀应用情况一样,最终会被归类到 REASON_SIGNALED,status 为 9 的情况。

应用不可实时感知类型

除上述情况外,其他的都属于应用不可实时感知的退出类型。

应用不可实时感知的退出情况,实际应该基本都是通过发送 signal 9 来触发应用进程退出的,对于 signal 9,应用无法基于信号处理机制捕获,系统则通过在触发应用退出前打点来做细分类型的区分。

其中正常的应用退出情况包括:

  • 应用授权状态变更触发应用退出,即 REASON_PERMISSION_CHANGE
  • REASON_USER_REQUESTED,用户操作触发的应用退出,包括任务栏上滑,点击 “Force stop” 强杀,应用更新等情况,subReason 中可能带有具体的理由
  • REASON_USER_STOPPED,系统多用户情况下做用户切换时触发的应用退出

异常的应用退出情况则包括:

  • REASON_LOW_MEMORY,即 Low Memory Killer 杀进程的情况
  • REASON_EXCESSIVE_RESOURCE_USAGE,和 Low Memory Killer 类似的因为系统资源紧张杀进程的情况,主要关注的是 CPU 开销,AMS 会周期性检查进程的 CPU Time 消耗
  • REASON_INITIALIZATION_FAILURE,应用因启动异常而被杀的情况,包括了启动过程中的一些超时以及预期外错误的情况。通常情况下我们说的 ContentProvider publish ANR,实际上并不会触发 AMS 的 appNotResponding 方法去做处理,便属于 REASON_INITIALIZATION_FAILURE
  • REASON_DEPENDENCY_DIED,应用因为自己的依赖变更而联带被杀,典型的情况是 stable 的 ContentProvider,Client 进程会随 Server 进程被杀。从系统源码看,WebView.apk 更新时,使用旧 WebView.apk 的应用似乎也会联带被杀,不过国内的情况大家通常都更喜欢用自己的魔改 Chromium 内核
  • 一些其他的零散情况包括:
    • REASON_OTHER,其他各类系统需要主动杀进程但又不好做原因归类的情况,subReason 中可能带有具体的理由
    • REASON_FREEZER,进程冻结时做拦截 Binder 通信处理时发生异常而杀进程。这里的进程冻结指的是 Android 11 官方给出的进程冻结机制,不过这个机制本身比较鸡肋,毕竟国内的几大厂商都早早就有了自己实现的类似功能
    • REASON_SIGNALED,应用因信号处理而退出,且没有被归类到前述这些类型的情况,比如前面提到的应用通过 Process.killProcess 自杀的情况
    • REASON_UNKNOWN,终极兜底类型了,简单翻系统源码也没找到具体的 case