什么是Crash?

  App Crash全称Application crash, 对于Crash可分为Java Crash和Native Crash。

  对于Crash所有的Android App开发者都会遇到,那么为什么会出现Crash呢?系统又是如何处理Crash的呢? 例如,在开发中大家经常使用try…catch语句来进行异常捕获,但还是会有一些异常是在运行中动态产生的,这些没有被有效捕获的异常就是导致应用Crash的原因。

小结:Crash是由于代码异常而导致App非正常退出现象,也就是我们常说的崩溃

系统的Crash 处理过程

1.1 Java Crash 产生过程

android crash 问题 crash for andorid_android


  上图是Android App启动时序图,我们想要探知的Crash秘密就存在于ZygoteInit.zygoteInit()函数中。下面让我们一起来看一下它的内部实现。

public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
       //重定向的System.out和System.err到Android日志
        RuntimeInit.redirectLogStreams();
       // **重点关注** 用于初始化一些App运行期间需要用到的配置
        RuntimeInit.commonInit();
       // 调用Native方法进行初始化
        ZygoteInit.nativeZygoteInit();
      	...
    }

   可以看到zygoteInit 是一个组装函数,下面让我们一探隐藏在内部的RuntimeInit.commonInit()函数。

protected static final void commonInit() {
        // LoggingHandler 用于组装异常信息并打印
        LoggingHandler loggingHandler = new LoggingHandler();
        // API 30 开始加入RuntimeHooks。RuntimeHooks.setUncaughtExceptionPreHandler()内部调用了Thread.setUncaughtExceptionPreHandler()
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        // KillApplicationHandler 用于弹出Dialog并杀死进程
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
		...
    }

   所以当Android App发生未被捕获的异常发生时会终止线程,此时系统便会调用UncaughtExceptionHandler,告诉它被出错的线程以及对应的异常,然后便会调用uncaughtException函数.如果UncaughtExceptionHandler没有被设置,则会调用对应线程组的DefaultUncaughtExceptionHandler

Thread.setUncaughtExceptionPreHandler覆盖所有线程,会在DefaultUncaughtExceptionHandler之前调用,只能在Android Framework内部调用该方法.

Thread.setDefaultUncaughtExceptionHandler任意线程中设置即可作为所有线程的默认异常处理,可以在应用层调用,每次调用传入的Thread.UncaughtExceptionHandler都会替换上一次的.

new Thread().setUncaughtExceptionHandler()只可以处理当前线程的异常,如果有Thread设置了UncaughtExceptionHandler,则在当前线程不会再使用全局的DefaultUncaughtExceptionHandler.

   LoggingHandler.Java

private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
 		// 用于标识是否已触发处理逻辑
        public volatile boolean mTriggered = false;
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            mTriggered = true;
          	// 如果 KillApplicationHandler.uncaughtException()已经被触发了则不再继续执行
            if (mCrashing) return;
            // 判断是否为系统进程,mApplicationObject == null,一定不是普通的app进程. 但是除了system进程, 也有可能是shell进程, 即通过app_process + 命令参数 的方式创建的进程
            if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
            } else {
                StringBuilder message = new StringBuilder();
                message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
                final String processName = ActivityThread.currentProcessName();
                if (processName != null) {
                    message.append("Process: ").append(processName).append(", ");
                }
                message.append("PID: ").append(Process.myPid());
                Clog_e(TAG, message.toString(), e);
            }
        }
    }
  • 当system进程Crash的信息:

*** FATAL EXCEPTION IN SYSTEM PROCESS [线程名]为开头;
在第二行开始输出发生Crash时的调用栈信息;

  • 其他进程Crash时的信息:

FATAL EXCEPTION: [线程名] 为开头
在第二行输出 Process: [进程名], PID: [进程id]
第三行开始输出发生Crash时的调用栈信息;

   KillApplicationHandler.Java

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        // 内部持有LoggingHandler用于输出异常信息。
        private final LoggingHandler mLoggingHandler;
        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }
        
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
            // 触发异常信息记录
                ensureLogging(t, e);
                //避免在处理崩溃信息时发生异常导致无限重入
                if (mCrashing) return;
                mCrashing = true;
				//进行意外停止分析
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }
                // 在此处弹出弹窗并杀死所在进程 **核心 见小节1.2** 
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
              ...
            } finally {
                // 通过finally语句块保证能执行并彻底杀掉Crash进程。当Crash进程被杀后,并没有完全结束,还有Binder死亡通知的流程还没有处理完成。
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }
        private void ensureLogging(Thread t, Throwable e) {
           // 调用mLoggingHandler组装并打印异常信息
        }
    }

   上面的代码注释很详细就不再赘述了。现在我们可以总结一下Android Crash的特点:

1. 一般情况下程序出错时会弹出提示框;
2. 程序所在进程被杀死,JVM虚拟机退出;
3. 系统提供了捕获Crash的接口;
4. 由Java 层代码引发的Java Crash 较容易捕获分析;
5. 有C++ 层代码引发的Native Crash 一般的工具不能将其捕获;

1.2 发生Crash 时系统都做了什么?

   在上一节中我们详细的梳理了Android Crash的产生过程,其中在KillApplicationHandler .uncaughtException()函数中我们看到了ActivityManager.getService().handleApplicationCrash()这样一个函数,其实这才是Android系统在发生Crash时所做事情的核心入口,接下来我们就将对其展开讲解。

  我们先来看一下处理Crash的流程图:

android crash 问题 crash for andorid_android crash 问题_02

   ActivityManager.getService()返回的是ActivityManagerProxy实例(简称AMP),AMP经过binder调用最终交给ActivityManagerService(简称AMS)中相应的方法去处理,故接下来调用的是AMS.handleApplicationCrash()。

1.2.1 AMS.handleApplicationCrash()

public void handleApplicationCrash(IBinder app,
            ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
        // 获取进程对象,见1.2.2小节
        ProcessRecord r = findAppProcess(app, "Crash");
        final String processName = app == null ? "system_server"
                : (r == null ? "unknown" : r.processName);
        // 见1.2.3小节        
        handleApplicationCrashInner("crash", r, processName, crashInfo);
    }

  关于进程名(processName):

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

1.2.1 AMS.findAppProcess()

private ProcessRecord findAppProcess(IBinder app, String reason) {
        if (app == null) {
            return null;
        }

        synchronized (this) {
            return 找到app对应的进程信息,如果没找到会返回Null;
        }
    }

1.2.3 AMS.handleApplicationCrashInner()

void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
 		//将Crash信息写入到 Event log
    	EventLog.writeEvent(EventLogTags.AM_CRASH,...);
    	//将错误信息添加到 DropBox
    	addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
    	// 见1.2.4
    	mAppErrors.crashApplication(r, crashInfo);
    }

addErrorToDropBox是将Crash的信息输出到目录/data/system/dropbox。例如system_server的dropbox文件名为system_server_crash@时间戳.txt

1.2.4 AppErrors.crashApplication()

void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
       	//清除远程调用者uid和pid信息,并保存到origId
       	final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        try {
        	if(存在ActivityController,比如monkey){
        		// 调用monkey的appCrashed
        		retutn;
        	}
        	  // 见1.2.5
        	if (!makeAppCrashingLocked()) {
            	Binder.restoreCallingIdentity(origId);
            	return;
        	}
        	...
        	//发送消息SHOW_ERROR_MSG,弹出提示crash的对话框,等待用户选择【见小节10】
        	mUiHandler.sendMessage(msg);
        	//进入阻塞等待,直到用户选择crash对话框
    		int res = result.get();
        } finally {
        	//恢复远程调用者uid和pid
            Binder.restoreCallingIdentity(origId);
        }
    }

   此方法主要做的两件事:

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

1.2.5 AppErrors.makeAppCrashingLocked()

private boolean makeAppCrashingLocked(ProcessRecord app,
            String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
        app.setCrashing(true);
        //封装crash信息到crashingReport对象
        app.crashingReport = generateProcessError(app,
                ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
        //  见1.2.6
        app.startAppProblemLocked();
        // 停止屏幕冻结 见1.2.7 
        app.getWindowProcessController().stopFreezingActivities();
        // 见1.2.8 
        return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace,
                data);
    }

1.2.6 ProcessRecord.startAppProblemLocked()

void startAppProblemLocked() {
       ComponentName errorReportReceiver = null;
        for (int userId : mService.mUserController.getCurrentProfileIds()) {
            if (this.userId == userId) {
              	// 获取获取当前用户下的Crash应用的 ErrorReceiver(需要在系统的设置中的错误报告功能开启时才会有值)
                errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                        mService.mContext, info.packageName, info.flags);
            }
        }
        //调用AMS忽略当前app的广播接收
        mService.skipCurrentReceiverLocked(this);
    }

   此方法主要做的两件事:

  1. 如果开启了错误报告功能,获取广播接收器的组件名称;
  2. 忽略发生Crash应用的所有广播接收;

1.2.7 ProcessRecord.stopFreezingActivities()

public void stopFreezingActivities(boolean force) {
  ...
  //其中activities类型为ArrayList<ActivityRecord>,停止进程里所有的Activity
  int i = activities.size();
    while (i > 0) {
        i--;
        activities.get(i).stopFreezingScreenLocked(true); 
    }
}

public void stopFreezingScreenLocked(boolean force) {
	// appToken是WindowManager的token。 见1.7.9
	WMS.stopAppFreezingScreen(appToken, force);
}

1.2.8 AppErrors.handleAppCrashLocked()

boolean handleAppCrashLocked(ProcessRecord app, String reason, String shortMsg, String longMsg, String stackTrace) {
    if(同一进程在1分钟内连续两次crash){
    	if(不是persistent进程){
    		// ActivityStackSupervisor 简称ASS
    		ASS.handleAppCrashLocked, 直接结束该应用所有activity
    		AMS.removeProcessLocked,杀死该进程以及同一个进程组下的所有进程
    	}
    	ASS.resumeTopActivitiesLocked,恢复栈顶第一个非finishing状态的activity
    }else{
    	ASS.finishTopRunningActivityLocked,执行结束栈顶正在运行activity
    }
}

1.2.9 WMS.stopFreezingScreenLocked()

public void stopFreezingScreen() {
    if (权限检查) {
        throw new SecurityException("Requires FREEZE_SCREEN permission");
    }
 
    synchronized(mWindowMap) {
     	1. 处理屏幕旋转相关逻辑;
	 	2. 移除冻屏的超时消息;
	 	3. 屏幕旋转动画的相关操作;
	 	4. 使能输入事件分发功能;
	 	5. display冻结时,执行gc操作;
	 	6. 更新当前的屏幕方向;
	 	7. 发送configuraion改变的消息。
    }
}

1.2.10 AMS.UiHandler

final class UiHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case SHOW_ERROR_MSG: 
		   1. 创建提示crash对话框,等待用户选择,5分钟操作等待。
		   2. 阻塞等待用户选择,当用户不做任何选择5分钟超时后,默认选择“确定”,当手机休眠时也默认选择“确定”
		 break;
        ...
    }
}

1.3 总结

  本文主要结合源码,详细介绍了App 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应用的ErrorReceiver,并忽略当前应用的广播;
停止当前进程中所有Activity中的WMS的冻结屏幕消息,并执行相关一些屏幕相关操作;
6. 执行handleAppCrashLocked方法,
当1分钟内同一进程连续Crash两次时,且非persistent进程,则直接结束该应用所有Activity,并杀死该进程以及同一个进程组下的所有进程。然后再恢复栈顶第一个非finishing状态的Activity;
当1分钟内同一进程连续crash两次时,且persistent进程,,则只执行恢复栈顶第一个非finishing状态的Activity;
当1分钟内同一进程未发生连续Crash两次时,则执行结束栈顶正在运行Activity的流程。

7. 通过UiHandler发送消息SHOW_ERROR_MSG,弹出Crash对话框;
8. system_server进程执行完成。回到Crash进程开始执行杀掉当前进程的操作;
9. 当Crash进程被杀,通过binder死亡通知,告知system_server进程来执行appDiedLocked();
10. 最后,执行清理应用相关的activity/service/ContentProvider/receiver组件信息。

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

小知识:
当60s内连续Crash两次的非persistent进程时,被认定为bad进程:那么如果第3次从后台启动该进程(Intent.getFlags来判断),则会拒绝创建进程;
当Crash次数达到两次的非persistent进程发生Crash时,则再次杀该进程,随后即便允许自启的Service也会在被杀后拒绝再次启动。