理解 Android Crash处理流程

基于Android 6.0的源码剖析, 分析Android应用Crash是如何处理的。

一、概述

App crash(全称Application crash), 对于Crash可分为native crash和framework crash(包含app crash在内),对于crash相信很多app开发者都会遇到,那么上层什么时候会出现crash呢,系统又是如何处理crash的呢。例如,在app大家经常使用try...catch语句,那么如果没有有效catch exception,就是导致应用crash,发生没有catch exception,系统便会来进行捕获,并进入crash流程。如果你是从事Android系统开发或者架构相关工作,或者遇到需要解系统性的疑难杂症,那么很有必要了解系统Crash处理流程,知其然还需知其所以然;如果你仅仅是App初级开发,可能本文并非很适合阅读,整个系统流程错中复杂。

在Android系统启动系列文章,已讲述过上层应用都是由Zygote fork孵化而来,分为system_server系统进程和各种应用进程,在这些进程创建之初会设置未捕获异常的处理器,当系统抛出未捕获的异常时,最终都交给异常处理器。

  • 对于system_server进程:文章Android系统启动-SystemServer上篇,system_server启动过程中由RuntimeInit.java的commonInit方法设置UncaughtHandler,用于处理未捕获异常;
  • 对于普通应用进程:文章理解Android进程创建流程 ,进程创建过程中,同样会调用RuntimeInit.java的commonInit方法设置UncaughtHandler。

1.1 crash调用链

crash流程的方法调用关系来结尾:

接下来说说这个过程。

二、Crash处理流程

那么接下来以commonInit()方法为起点来展开说明。

1. RuntimeInit.commonInit

setDefaultUncaughtExceptionHandler()只是将异常处理器handler对象赋给Thread成员变量,即Thread.defaultUncaughtHandler = new UncaughtHandler()。接下来看看UncaughtHandler对象实例化过程。

2. UncaughtHandler

[–>RuntimeInit.java]

  1. 当system进程crash的信息:
    • 开头*** FATAL EXCEPTION IN SYSTEM PROCESS [线程名]
    • 接着输出发生crash时的调用栈信息;
  2. 当app进程crash时的信息:
    • 开头FATAL EXCEPTION: [线程名]
    • 紧接着 Process: [进程名], PID: [进程id]
    • 最后输出发生crash时的调用栈信息。

看到这里,你就会发现要从log中搜索crash信息,只需要搜索关键词FATAL EXCEPTION;如果需要进一步筛选只搜索系统crash信息,则可以搜索的关键词可以有多样,比如*** FATAL EXCEPTION

当输出完crash信息到logcat里面,这只是crash流程的刚开始阶段,接下来弹出crash对话框,ActivityManagerNative.getDefault()返回的是ActivityManagerProxy(简称AMP),AMP经过binder调用最终交给ActivityManagerService(简称AMS)中相应的方法去处理,故接下来调用的是AMS.handleApplicationCrash()。

2.1 CrashInfo

[-> ApplicationErrorReport.java]

将crash信息文件名类名方法名对应行号以及异常信息都封装到CrashInfo对象。

3. handleApplicationCrash

[–>ActivityManagerService.java]

关于进程名(processName):

  1. 当远程IBinder对象为空时,则进程名为system_server
  2. 当远程IBinder对象不为空,且ProcessRecord为空时,则进程名为unknown;
  3. 当远程IBinder对象不为空,且ProcessRecord不为空时,则进程名为ProcessRecord对象中相应进程名。

3.1 findAppProcess

[–>ActivityManagerService.java]

其中 mProcessNames = new ProcessMap<ProcessRecord>();对于代码mProcessNames.getMap()返回的是mMap,而mMap= new ArrayMap<String, SparseArray<ProcessRecord>>();

知识延伸:SparseArrayArrayMap是Android专门针对内存优化而设计的取代Java API中的HashMap的数据结构。对于key是int类型则使用SparseArray,可避免自动装箱过程;对于key为其他类型则使用ArrayMapHashMap的查找和插入时间复杂度为O(1)的代价是牺牲大量的内存来实现的,而SparseArrayArrayMap性能略逊于HashMap,但更节省内存。

再回到mMap,这是以进程name为key,再以(uid为key,以ProcessRecord为Value的)结构体作为value。下面看看其get()和put()方法

findAppProcess()根据app(IBinder类型)来查询相应的目标对象ProcessRecord。

有了进程记录对象ProcessRecord和进程名processName,则进入执行Crash处理方法,继续往下看。

4. handleApplicationCrashInner

[–>ActivityManagerService.java]

其中addErrorToDropBox是将crash的信息输出到目录/data/system/dropbox。例如system_server的dropbox文件名为system_server_crash@xxx.txt (xxx代表的是时间戳)

5. crashApplication

[–>ActivityManagerService.java]

该方法主要做的两件事:

  • 调用makeAppCrashingLocked,继续处理crash流程;
  • 发送消息SHOW_ERROR_MSG,弹出提示crash的对话框,等待用户选择;

6. makeAppCrashingLocked

[–>ActivityManagerService.java]

7. startAppProblemLocked

[–>ActivityManagerService.java]

该方法主要功能:

  • 获取当前用户下的crash应用的error receiver;
  • 忽略当前app的广播接收;

7.1 getErrorReportReceiver

[-> ApplicationErrorReport.java]

getErrorReportReceiver:这是同名不同输入参数的另一个方法:

7.2 skipCurrentReceiverLocked

[–>ActivityManagerService.java]

7.2.1 skipCurrentReceiverLocked

[-> BroadcastQueue.java]

8. PR.stopFreezingAllLocked

[-> ProcessRecord.java]

其中activities类型为ArrayList<ActivityRecord>,停止进程里所有的Activity

8.1. AR.stopFreezingScreenLocked

[-> ActivityRecord.java]

其中appToken是IApplication.Stub类型,即WindowManager的token。

8.1.1 WMS.stopFreezingScreenLocked

[-> WindowManagerService.java]

8.1.1.1 WMS.stopFreezingDisplayLocked

[-> WindowManagerService.java]

该方法主要功能:

  1. 处理屏幕旋转相关逻辑;
  2. 移除冻屏的超时消息;
  3. 屏幕旋转动画的相关操作;
  4. 使能输入事件分发功能;
  5. display冻结时,执行gc操作;
  6. 更新当前的屏幕方向;
  7. 向mH发送configuraion改变的消息。

9.AMS.handleAppCrashLocked

[-> ActivityManagerService.java]

  1. 当同一进程在时间间隔小于1分钟时连续两次crash,则执行的情况下:
    • 对于非persistent进程:
      • [9.1] mStackSupervisor.handleAppCrashLocked(app);
      • [9.2] removeProcessLocked(app, false, false, “crash”);
      • [9.3] mStackSupervisor.resumeTopActivitiesLocked();
    • 对于persistent进程,则只执行
      • [9.3] mStackSupervisor.resumeTopActivitiesLocked();
  2. 否则执行
    • [9.4] mStackSupervisor.finishTopRunningActivityLocked(app, reason);

9.1 ASS.handleAppCrashLocked

[-> ActivityStackSupervisor.java]

9.1.1 AS.handleAppCrashLocked

[-> ActivityStack.java]

这里的mTaskHistory数据类型为ArrayList,记录着所有先前的后台activities。遍历所有activities,找到位于该ProcessRecord的所有ActivityRecord,并结束该Acitivity。

9.2 AMS.removeProcessLocked

[-> ActivityManagerService.java]

  • mProcessNames数据类型为ProcessMap,这是以进程名为key,记录着所有的ProcessRecord信息
  • mPidsSelfLocked数据类型为SparseArray,这是以pid为key,记录着所有的ProcessRecord信息。该对象的同步保护是通过自身锁,而非全局ActivityManager锁。
9.2.1 app.kill

[-> ProcessRecord.java]

此处reason为“crash”,关于杀进程的过程见我的另一篇文章理解杀进程的实现原理.

9.2.2 handleAppDiedLocked

[-> ActivityManagerService.java]

9.3 ASS.resumeTopActivitiesLocked

[-> ActivityStackSupervisor.java]

此处mFocusedStack是当前正在等待接收input事件或者正在启动下一个activity的ActivityStack

9.3.1 AS.resumeTopActivityLocked

[-> ActivityStack.java]

9.3.2 AS.resumeTopActivityInnerLocked

[-> ActivityStack.java]

该方法代码比较长,这里就简单列举几条比较重要的代码。执行完该方法,应用也便完成了activity的resume过程。

9.4 finishTopRunningActivityLocked

9.4.1 ASS.finishTopRunningActivityLocked

[-> ActivityStackSupervisor.java]

9.4.2 AS.finishTopRunningActivityLocked

9.4.3 AS.finishActivityLocked

该方法最终会回调到activity的pause方法。

执行到这,我们还回过来看小节5.crashApplication中,处理完makeAppCrashingLocked,则会再发送消息SHOW_ERROR_MSG,弹出提示crash的对话框,接下来再看看该过程。

10. UiHandler

通过mUiHandler发送message,且消息的msg.waht=SHOW_ERROR_MSG,接下来进入UiHandler来看看handleMessage的处理过程。

[-> ActivityManagerService.java]

在发生crash时,默认系统会弹出提示crash的对话框,并阻塞等待用户选择是“退出”或 “退出并报告”,当用户不做任何选择时5min超时后,默认选择“退出”,当手机休眠时也默认选择“退出”。到这里也并没有真正结束,在小节2.uncaughtException中在finnally语句块还有一个杀进程的动作。

11. killProcess

通过finnally语句块保证能执行并彻底杀掉Crash进程,关于杀进程的过程见我的另一篇文章理解杀进程的实现原理.。当Crash进程被杀后,并没有完全结束,还有Binder死亡通知的流程还没有处理完成。

12. 小结

当进程抛出未捕获异常时,则系统会处理该异常并进入crash处理流程。

app_crash

其中最为核心的工作图中红色部分AMS.handleAppCrashLocked的主要功能:

  1. 当同一进程1分钟之内连续两次crash,则执行的情况下:
    • 对于非persistent进程:
      • ASS.handleAppCrashLocked, 直接结束该应用所有activity
      • AMS.removeProcessLocked,杀死该进程以及同一个进程组下的所有进
      • ASS.resumeTopActivitiesLocked,恢复栈顶第一个非finishing状态的activity
    • 对于persistent进程,则只执行
      • ASS.resumeTopActivitiesLocked,恢复栈顶第一个非finishing状态的activity
  2. 否则,当进程没连续频繁crash
    • ASS.finishTopRunningActivityLocked,执行结束栈顶正在运行activity

另外,AMS.handleAppCrashLocked,该方法内部主要调用链,如下:

三、Binder死亡通知

进程被杀,如果还记得Binder的死亡回调机制,在应用进程创建的过程中有一个attachApplicationLocked方法的过程中便会创建死亡通知。

[-> ActivityManagerService.java]

当binder服务端挂了之后,便会通过binder的DeathRecipient来通知AMS进行相应的清理收尾工作。前面已经降到crash的进程会被kill掉,那么当该进程会杀,则会回调到binderDied()方法。

1. binderDied

[-> ActivityManagerService.java]

2. appDiedLocked

3 handleAppDiedLocked

[-> ActivityManagerService.java]

4 cleanUpApplicationRecordLocked

该方法清理应用程序service, BroadcastReceiver, ContentProvider,process相关信息,为了便于说明将该方法划分为4个部分讲解

4.1 清理service

参数restarting = false, allowRestart =true, index =-1

  • mProcessesToGc:记录着需要尽快执行gc的进程列表
  • mPendingPssProcesses:记录着需要收集内存信息的进程列表

4.2 清理ContentProvider

4.3 清理BroadcastReceiver

4.4 清理Process

对于需要重启进程的情形有:

  • mLaunchingProviders:记录着存在client端等待的ContentProvider。应用当前正在启动中,当ContentProvider一旦发布则将该ContentProvider将从该list去除。当进程包含这样的ContentProvider,则需要重启进程。
  • mPersistentStartingProcesses:记录着试图在系统ready之前就启动的进程。在那时并不启动这些进程,先记录下来,等系统启动完成则启动这些进程。当进程属于这种类型也需要重启。

5. 小结

当crash进程执行kill操作后,进程被杀。此时需要掌握binder 死亡通知原理,由于Crash进程中拥有一个Binder服务端ApplicationThread,而应用进程在创建过程调用attachApplicationLocked(),从而attach到system_server进程,在system_server进程内有一个ApplicationThreadProxy,这是相对应的Binder客户端。当Binder服务端ApplicationThread所在进程(即Crash进程)挂掉后,则Binder客户端能收到相应的死亡通知,从而进入binderDied流程。更多关于bInder原理,这里就不细说,博客中有关于binder系列的专题。

binder_died

四、 总结

本文主要以源码的视角,详细介绍了到应用crash后系统的处理流程:

  1. 首先发生crash所在进程,在创建之初便准备好了defaultUncaughtHandler,用来来处理Uncaught Exception,并输出当前crash基本信息;
  2. 调用当前进程中的AMP.handleApplicationCrash;经过binder ipc机制,传递到system_server进程;
  3. 接下来,进入system_server进程,调用binder服务端执行AMS.handleApplicationCrash;
  4. mProcessNames查找到目标进程的ProcessRecord对象;并将进程crash信息输出到目录/data/system/dropbox
  5. 执行makeAppCrashingLocked
    • 创建当前用户下的crash应用的error receiver,并忽略当前应用的广播;
    • 停止当前进程中所有activity中的WMS的冻结屏幕消息,并执行相关一些屏幕相关操作;
  6. 再执行handleAppCrashLocked方法,
    • 当1分钟内同一进程连续crash两次时,且非persistent进程,则直接结束该应用所有activity,并杀死该进程以及同一个进程组下的所有进程。然后再恢复栈顶第一个非finishing状态的activity;
    • 当1分钟内同一进程连续crash两次时,且persistent进程,,则只执行恢复栈顶第一个非finishing状态的activity;
    • 当1分钟内同一进程未发生连续crash两次时,则执行结束栈顶正在运行activity的流程。
  7. 通过mUiHandler发送消息SHOW_ERROR_MSG,弹出crash对话框;
  8. 到此,system_server进程执行完成。回到crash进程开始执行杀掉当前进程的操作;
  9. 当crash进程被杀,通过binder死亡通知,告知system_server进程来执行appDiedLocked();
  10. 最后,执行清理应用相关的activity/service/ContentProvider/receiver组件信息。

这基本就是整个应用Crash后系统的执行过程。

作者微博:@Gityuan

原文出处:Gityuan.com

1 1 收藏 评论

关于作者:gityuan

Android系统工程师,www.gityuan.com博主,个人新浪微博:http://weibo.com/gityuan 个人主页 · 我的文章 · 3 ·     

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部