Android稳定性之java crash

概述:

Android是基于java,虚拟机采用的是ART.而在应用进程启动的时候,在通过runtimeinit中的commoninit方法,设置了进程的defaultuncaughtexceptionhandler。那我们就从这里入手。让我们带着问题来一起看一下。本文基于Android Q版本。
1.什么时候设置的uncaughtexceptionhandler?
2.什么时候会触发uncaughtexceptionhandler?

第一个问题:

img

    @UnsupportedAppUsage
    protected static final void commonInit() {
        // 在commoninit的时候创建了killapplicationhandler , 其中的logginghandler只是为了打印log
       RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
       Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
    } 

    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                // 打印log
                ensureLogging(t, e);
                // bindercall到systemserver
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                ...
            } finally {
                // 确保彻底杀死了本进程
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

上面这些其实都是发生在应用进程中,当应用发生崩溃的时候,就会通过binder调用,来将crashinfo传递给systemserver,而parcelablecrashinfo也是通过throwable e来进行封装创建的。
下面就通过binder调用,来看看systemserver所进行的操作。

ams->handleApplicationCrash

    public void handleApplicationCrash(IBinder app,
            ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
        ProcessRecord r = findAppProcess(app, "Crash");
        final String processName = app == null ? "system_server"
                : (r == null ? "unknown" : r.processName);

        handleApplicationCrashInner("crash", r, processName, crashInfo);
    }

首先通过ibinder找到对应的processrecord,然后调用handleapplicationcrashinner

    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
        // 省略打印log的部分

        final int relaunchReason = r == null ? RELAUNCH_REASON_NONE
                        : r.getWindowProcessController().computeRelaunchReason();
        final String relaunchReasonString = relaunchReasonToString(relaunchReason);
        if (crashInfo.crashTag == null) {
            crashInfo.crashTag = relaunchReasonString;
        } else {
            crashInfo.crashTag = crashInfo.crashTag + " " + relaunchReasonString;
        }
        // 将错误信息打印到dropbox
        addErrorToDropBox(
                eventType, r, processName, null, null, null, null, null, null, crashInfo);

        mAppErrors.crashApplication(r, crashInfo);
    }
    // AppErrors.java
    void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
            int callingPid, int callingUid) {
        AppErrorResult result = new AppErrorResult();
        int taskId;
        synchronized (mService) {
                // 在monkey的情况
            if (handleAppCrashInActivityController(r, crashInfo, shortMsg, longMsg, stackTrace,
                    timeMillis, callingPid, callingUid)) {
                return;
            }        

            
            AppErrorDialog.Data data = new AppErrorDialog.Data();
            data.result = result;
            data.proc = r;

            if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) {
                return;
            }

            final Message msg = Message.obtain();
            msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;

            taskId = data.taskId;
            msg.obj = data;
            mService.mUiHandler.sendMessage(msg);
        }
        // 弹出系统错误对话框,等待用户点击
        int res = result.get();  
        ...
    }


    private boolean makeAppCrashingLocked(ProcessRecord app,
            String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
        app.setCrashing(true);
        app.crashingReport = generateProcessError(app,
                ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
        app.startAppProblemLocked();
        app.getWindowProcessController().stopFreezingActivities();
        return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace,
                data);
    }

其实后面就是对应的ams和wms相关的系统层面的操作了。如切换当前的topapp,修改对应的app的windows状态,以及input的focus问题。这里就不赘述了。
那么其实现在就可以回答第一个问题了: 什么时候设置的uncaughtexceptionhandler?
答案就是:在应用启动的时候,系统会在runtimeinit的commoninit设置对应的defaultuncaughtexceptionhandler。

第二个问题:

我们都知道,Android进程其实是运行在ART虚拟机当中,当运行的代码发生了错误的时候,虚拟机会调用Thread::HandleUncaughtExceptions

// thread.cc
void Thread::HandleUncaughtExceptions(ScopedObjectAccessAlreadyRunnable& soa) {
  // Call the Thread instance's dispatchUncaughtException(Throwable)
  tlsPtr_.jni_env->CallVoidMethod(peer.get(),
      WellKnownClasses::java_lang_Thread_dispatchUncaughtException,
      exception.get());
}

通过jni的方式调用到了java层

// libcore/ojluni/src/main/java/java/lang/Thread.java
    public final void dispatchUncaughtException(Throwable e) {
        // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
        Thread.UncaughtExceptionHandler initialUeh =
                Thread.getUncaughtExceptionPreHandler();
        if (initialUeh != null) {
            try {
                initialUeh.uncaughtException(this, e);
            } catch (RuntimeException | Error ignored) {
                // Throwables thrown by the initial handler are ignored
            }
        }
        // END Android-added: uncaughtExceptionPreHandler for use by platform.
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

上面的流程中就可以和第一个问题串联在一起了。在发生异常的时候,虚拟机得到对应的uncaughtexceptionhandler,调用其对应的uncaughtexception方法。
但是需要注意的是,这里有两个exceptionhandler的实现。分别是PreHandlerHandler
需要注意的是:
1.setDefaultUncaughtExceptionHandler是公开API。应用可通过调用,自定义。
2.setUncaughtExceptionPreHandler是hidden API。应用无法调用,不能替换
具体的可以查看这个链接,从注释中可以看到,这么做的目的,是为了不管应用是否替换了handler,系统都能够打印出对应的崩溃日志。
那么我们就可以回答第二个问题了:
在ART虚拟机发生错误的时候,会调用注册在thread中的handler,通过handler的uncaughtexception来告知当前线程发生了崩溃。

总结:

javacrash,因为都是发生在ART虚拟机中,而ART虚拟机在启动的时候,注册了2个handler来处理错误,一个用来打印log,而另外一个用来杀死进程。APP可以通过设置第二个handler来进行自定义的java crash的处理。