Android crash 流程详解(一):JE

源码基于:Android R

0. 前言

App crash(全称为 Application crash),又分 java crash 和 native crash,又称 java layer exception(JE) 和 native layer exception(NE)。对于 crash 在开发过程中或多或少都会遇到,本文将整理总结 crash 原理,剖析系统是如何捕捉、处理这些 crash。因为篇幅较长,所以会分JE 和 NE 两部分各自剖析。

1. RuntimeInit.commonInit()

在Andriod 系统中,上层应用都是由 Zygote fork 孵化而来,分为system_server 系统进程和普通应用进程。在这些进程创建之初会设置未捕获异常的处理器,当系统抛出未捕获的异常时,最终都会交给异常处理器。而这个异常捕获的处理设置就是在 RuntimeInit.commonInit() 函数中完成:

frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

  protected static final void commonInit() {
        ...

        LoggingHandler loggingHandler = new LoggingHandler();
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

        ...
    }

1.1 Thread.setDefaultUncaughtExceptionHandler()

libcore/ojluni/src/main/java/java/lang/Thread.java

    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
         defaultUncaughtExceptionHandler = eh;
     }

参数的类型为 UncaughtExceptionHandler,这就是未捕获的异常类型。该类型是 interface,后面调用其 uncaughtException() 函数进行处理:

libcore/ojluni/src/main/java/java/lang/Thread.java

    public interface UncaughtExceptionHandler {
        void uncaughtException(Thread t, Throwable e);
    }

当线程因为未捕获的异常停止时,Java 虚拟机会调用 uncaughtException() 函数。

1.2 KillApplicationHandler 类

从上面的代码看到参数 UncaughtExceptionHandler 为 KillApplicationHandler 对象,这里来看下该类:

frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;

        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            ...
        }

        ...
    }

该类父类为 Thread.UncaughtExceptionHandler,并实现了 uncaughtException() 函数。

另外,构造中需要传入一个 LoggingHandler 的对象,存放在私有变量 mLoggingHandler 中。

对于核心的处理函数 uncaughtException() 详细见下面第 2 节。

1.3 LoggingHandler 类

frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
 
   private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
        public volatile boolean mTriggered = false;

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            mTriggered = true;

             //已经在crash 流程中,则已经在处理KillApplicationHandler则不再重复进入
            if (mCrashing) return;

            if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
            } else {
                logUncaught(t.getName(), ActivityThread.currentProcessName(), Process.myPid(), e);
            }
        }
    }

该类中的 uncaughtException() 是在crash 最开始调用的,用以输出crash 开头信息:

  • 当 system 进程crash提示 *** FATAL EXCEPTION IN SYSTEM PROCESS: [线程名]
  • 当 app 进程crash 提示三个内容:
    • FATAL EXCEPTION: [线程名]
    • Process: [进程名], PID: [pid]

对于processName 为null,只会提示PID。

2. KillApplicationHandler.uncaughtException()

当线程因为未捕获的异常停止时,Java 虚拟机会调用 uncaughtException() 函数,即调用 KillApplicationHandler 中的 uncaughtException() 函数。本节将在上面第 1.2 节的基础上详细地剖析uncaughtException() 函数:

frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

       public void uncaughtException(Thread t, Throwable e) {
            try {
                //调用LoggingHandler.uncaughtException(),不会反复调用
                ensureLogging(t, e);

                //全局变量,用以控制重复进入crash流程,第一次进入后会将该变量置true
                if (mCrashing) return;
                mCrashing = true;

                //尝试去停止profiling,因为后面需要kill 进程,内存buffer会丢失,
                //所以尝试停止,来 flush 内存buffer
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }

                //弹出crash对话框,等待处理完成
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                ...
            } finally {
                //确保当前进程彻底杀掉
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

主要是通过 AMS 调用 handleApplicationCrash() 函数进行 crash report,共两个参数:

  • 第一个参数为进程对象
  • 第二个参数为ParcelableCrashInfo(父类为 CrashInfo 和 Parcelable)

ParcelableCrashInfo 用以封装 CrashInfo,引入Parcelable。

CrashInfo 类主要是保存 crash 信息:文件名、类名、方法名、对应行号、异常信息等。

详细的 handleApplicationCrash() 函数剖析,请查看下面第 3 节。

3. AMS.handleApplicationCrash()

frameworks/base/services/core/java/com/android/server/am/AMS.java


    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);
    }

该函数主要两个操作:

  • 确定进程名;
  • handleApplicationCrashInner() 函数调用;

对于进程名,

  • 当参数 app 为null,表示 system_server 进程;
  • 当参数 app不为null,通过findAppProcess() 确认ProcessRecord,进而确认进程名;

3.1 AMS.handleApplicationCrashInner()

frameworks/base/services/core/java/com/android/server/am/AMS.java

    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {

        EventLogTags.writeAmCrash(Binder.getCallingPid(),
                UserHandle.getUserId(Binder.getCallingUid()), processName,
                r == null ? -1 : r.info.flags,
                crashInfo.exceptionClassName,
                crashInfo.exceptionMessage,
                crashInfo.throwFileName,
                crashInfo.throwLineNumber);

        FrameworkStatsLog.write(FrameworkStatsLog.APP_CRASH_OCCURRED,
                Binder.getCallingUid(),
                eventType,
                processName,
                Binder.getCallingPid(),
                (r != null && r.info != null) ? r.info.packageName : "",
                (r != null && r.info != null) ? (r.info.isInstantApp()
                        ? FrameworkStatsLog.APP_CRASH_OCCURRED__IS_INSTANT_APP__TRUE
                        : FrameworkStatsLog.APP_CRASH_OCCURRED__IS_INSTANT_APP__FALSE)
                        : FrameworkStatsLog.APP_CRASH_OCCURRED__IS_INSTANT_APP__UNAVAILABLE,
                r != null ? (r.isInterestingToUserLocked()
                        ? FrameworkStatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__FOREGROUND
                        : FrameworkStatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__BACKGROUND)
                        : FrameworkStatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__UNKNOWN,
                processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER
                        : (r != null) ? r.getProcessClassEnum()
                                      : ServerProtoEnums.ERROR_SOURCE_UNKNOWN
        );

        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;
        }

        addErrorToDropBox(
                eventType, r, processName, null, null, null, null, null, null, crashInfo);

        mAppErrors.crashApplication(r, crashInfo);
    }

函数比较长,主要做了下面几件事情:

  • 写event log;

类似:

12-01 16:45:29.663198  1260  3220 I am_crash: [21597,0,com.qualcomm.qti.PresenceApp,550026821,java.lang.NoSuchMethodException,com.qualcomm.qti.PresenceApp.SubsriptionTab.<init> [],Class.java,2363]
  • addErrorToDropBox() 将crash 的信息输出到 /data/system/dropbox/ 下,例如system_server 的dropbox 文件名为 [email protected] (xxx 代表时间戳);
  • crashApplication() 继续处理 crash 流程,发出 SHOW_ERROR_UI_MSG,弹出 crash 对话框;

4.  AppErrors.crashApplication()

frameworks/base/services/core/java/com/android/server/am/AppErrors.java

    void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();

        final long origId = Binder.clearCallingIdentity();
        try {
            crashApplicationInner(r, crashInfo, callingPid, callingUid);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

主要是调用私有函数 crashApplicationInner(),因为代码逻辑太多,这里不过多剖析,主要关注如下几点:

  • handleAppCrashInActivityController()

如果是 IActivityController 类型该处理的 crash,是不会弹出对话框,通过该函数进入 makeAppCrashingLocked() 流程。

  • makeAppCrashingLocked()

如果无法识别进程,或者进程已经超过crash 额度,将不再弹出对话框,而是直接return到上一级。

  • 发出 SHOW_ERROR_UI_MSG 消息,弹出crash 对话框,详细看下面第 6 节;
  • 等待用户选择,根据不同选择做进一步处理

5. makeAppCrashingLocked()

frameworks/base/services/core/java/com/android/server/am/AppErrors.java

    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);
        app.startAppProblemLocked();
        app.getWindowProcessController().stopFreezingActivities();
        return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace,
                data);
    }

5.1 generateProcessError()

frameworks/base/services/core/java/com/android/server/am/AppErrors.java

    ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app,
            int condition, String activity, String shortMsg, String longMsg, String stackTrace) {
        ActivityManager.ProcessErrorStateInfo report = new ActivityManager.ProcessErrorStateInfo();

        report.condition = condition;
        report.processName = app.processName;
        report.pid = app.pid;
        report.uid = app.info.uid;
        report.tag = activity;
        report.shortMsg = shortMsg;
        report.longMsg = longMsg;
        report.stackTrace = stackTrace;

        return report;
    }

通过该函数创建 ActivityManager.ProcessErrorStateInfo 对象,并保存在 app.crashingReport 对象中。

5.2 startAppProblemLocked()

frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java

    void startAppProblemLocked() {
        // If this app is not running under the current user, then we can't give it a report button
        // because that would require launching the report UI under a different user.
        errorReportReceiver = null;

        for (int userId : mService.mUserController.getCurrentProfileIds()) {
            if (this.userId == userId) {
                errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                        mService.mContext, info.packageName, info.flags);
            }
        }
        mService.skipCurrentReceiverLocked(this);
    }

当crash 的app 是当前user 下,会通过 getErrorReportReceiver() 获取应用的report receiver,该receiver 指定的Intent 的action 为:android.intent.action.APP_ERROR。如果没有找到则使用prop ro.error.receiver.default 指定的,否则返回null。

5.2.1 getErrorReportReceiver()

frameworks/base/core/java/android/app/ApplicationErrorReport.java

    public static ComponentName getErrorReportReceiver(Context context,
            String packageName, int appFlags) {
        //首先global表中需要enable send_action_app_error属性,否则return null
        int enabled = Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.SEND_ACTION_APP_ERROR, 0);
        if (enabled == 0) {
            return null;
        }

        PackageManager pm = context.getPackageManager();

        // look for receiver in the installer package
        String candidate = null;
        ComponentName result = null;

        try {
            //获取该crash应用的包名
            candidate = pm.getInstallerPackageName(packageName);
        } catch (IllegalArgumentException e) {
            // the package could already removed
        }

        //第一次获取,使用的是crash应用的包名
        if (candidate != null) {
            result = getErrorReportReceiver(pm, packageName, candidate);
            if (result != null) {
                return result;
            }
        }

        //第二次获取,使用prop ro.error.receiver.system.apps指定的包名
        if ((appFlags&ApplicationInfo.FLAG_SYSTEM) != 0) {
            candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY);
            result = getErrorReportReceiver(pm, packageName, candidate);
            if (result != null) {
                return result;
            }
        }

        //第三次获取,使用prop ro.error.receiver.default指定的包名
        candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY);
        return getErrorReportReceiver(pm, packageName, candidate);
    }

上面最终会调用 getErrorReportReceiver() 的另一个函数,最后的参数为最终的候选包名:

frameworks/base/core/java/android/app/ApplicationErrorReport.java

   static ComponentName getErrorReportReceiver(PackageManager pm, String errorPackage,
            String receiverPackage) {
        //候选的包名为null 或为空,返回null
        if (receiverPackage == null || receiverPackage.length() == 0) {
            return null;
        }

        // 如果与crash的应用包名相同
        if (receiverPackage.equals(errorPackage)) {
            return null;
        }

        //action 为android.intent.action.APP_ERROR
        Intent intent = new Intent(Intent.ACTION_APP_ERROR);
        intent.setPackage(receiverPackage);
        ResolveInfo info = pm.resolveActivity(intent, 0);
        if (info == null || info.activityInfo == null) {
            return null;
        }
        return new ComponentName(receiverPackage, info.activityInfo.name);
    }

需要注意的是寻找这个report receiver 是为了在此处makeAppCrashingLocked() 函数返回,crash 对话框弹出之后,根据用户的选择做进一步的处理,包括了通过该 receiver 发从 report 信息:

frameworks/base/services/core/java/com/android/server/am/AppErrors.java

    Intent createAppErrorIntentLocked(ProcessRecord r,
            long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
        ApplicationErrorReport report = createAppErrorReportLocked(r, timeMillis, crashInfo);
        if (report == null) {
            return null;
        }
        Intent result = new Intent(Intent.ACTION_APP_ERROR);
        result.setComponent(r.errorReportReceiver);
        result.putExtra(Intent.EXTRA_BUG_REPORT, report);
        result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        return result;
    }

5.2.2 AMS.skipCurrentReceiverLocked()

    void skipCurrentReceiverLocked(ProcessRecord app) {
        for (BroadcastQueue queue : mBroadcastQueues) {
            queue.skipCurrentReceiverLocked(app);
        }
    }
frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

    public void skipCurrentReceiverLocked(ProcessRecord app) {
        BroadcastRecord r = null;
        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
        if (curActive != null && curActive.curApp == app) {
            // confirmed: the current active broadcast is to the given app
            r = curActive;
        }

        // If the current active broadcast isn't this BUT we're waiting for
        // mPendingBroadcast to spin up the target app, that's what we use.
        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                    "[" + mQueueName + "] skip & discard pending app " + r);
            r = mPendingBroadcast;
        }

        if (r != null) {
            skipReceiverLocked(r);
        }
    }

    private void skipReceiverLocked(BroadcastRecord r) {
        logBroadcastReceiverDiscardLocked(r);
        finishReceiverLocked(r, r.resultCode, r.resultData,
                r.resultExtras, r.resultAbort, false);
        scheduleBroadcastsLocked();
    }

主要是结束 app进程的广播。

5.3 stopFreezingActivities()

frameworks/base/services/core/java/com/android/server/am/WindowProcessController.java

    public void stopFreezingActivities() {
        synchronized (mAtm.mGlobalLock) {
            int i = mActivities.size();
            while (i > 0) {
                i--;
                mActivities.get(i).stopFreezingScreenLocked(true);
            }
        }
    }

其中的 mActivities 为 ArrayList<ActivityRecord>,停止进程里所有activity。

5.3.1 stopFreezingScreenLocked()

frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

    void stopFreezingScreenLocked(boolean force) {
        if (force || frozenBeforeDestroy) {
            frozenBeforeDestroy = false;
            if (getParent() == null) {
                return;
            }
            ProtoLog.v(WM_DEBUG_ORIENTATION,
                        "Clear freezing of %s: visible=%b freezing=%b", appToken,
                                isVisible(), isFreezingScreen());
            stopFreezingScreen(true, force);
        }
    }

最终调用的是 WSM.stopFreezingDisplayLocked() 函数,详细可以查看源码,大致流程:

  • 将冻屏相关的信息remove 掉;
  • 屏幕旋转动画的相关操作;
  • display 冻结时,执行GC 操作;
  • 更新当前的屏幕方向;

5.4 handleAppCrashLocked()

这个crash 后续操作流程的核心处理函数:

frameworks/base/services/core/java/com/android/server/am/AppErrors.java

    boolean handleAppCrashLocked(ProcessRecord app, String reason,
            String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
        final long now = SystemClock.uptimeMillis();
        final boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;

        final boolean procIsBoundForeground =
            (app.getCurProcState() == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);

        Long crashTime;
        Long crashTimePersistent;
        boolean tryAgain = false;

        if (!app.isolated) {
            crashTime = mProcessCrashTimes.get(app.info.processName, app.uid);
            crashTimePersistent = mProcessCrashTimesPersistent.get(app.info.processName, app.uid);
        } else {
            crashTime = crashTimePersistent = null;
        }

        // 确认crash进程任何services的crash count,确认是否置tryAgain为true
        for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
            // Any services running in the application need to be placed
            // back in the pending list.
            ServiceRecord sr = app.getRunningServiceAt(i);
            // If the service was restarted a while ago, then reset crash count, else increment it.
            if (now > sr.restartTime + ProcessList.MIN_CRASH_INTERVAL) {
                sr.crashCount = 1;
            } else {
                sr.crashCount++;
            }
            // Allow restarting for started or bound foreground services that are crashing.
            // This includes wallpapers.
            if (sr.crashCount < mService.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
                    && (sr.isForeground || procIsBoundForeground)) {
                tryAgain = true;
            }
        }

        //如果两次crash 的间隔小于1分钟,则认为crash太过频繁
        if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) {
            // The process crashed again very quickly. If it was a bound foreground service, let's
            // try to restart again in a while, otherwise the process loses!
            Slog.w(TAG, "Process " + app.info.processName
                    + " has crashed too many times: killing!");
            EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
                    app.userId, app.info.processName, app.uid);
            mService.mAtmInternal.onHandleAppCrash(app.getWindowProcessController());
            if (!app.isPersistent()) {
                // We don't want to start this process again until the user
                // explicitly does so...  but for persistent process, we really
                // need to keep it running.  If a persistent process is actually
                // repeatedly crashing, then badness for everyone.
                EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
                        app.info.processName);
                if (!app.isolated) {
                    // XXX We don't have a way to mark isolated processes
                    // as bad, since they don't have a persistent identity.
                    markBadProcess(app.info.processName, app.uid,
                            new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
                    mProcessCrashTimes.remove(app.processName, app.uid);
                }
                app.bad = true;
                app.removed = true;

                // 不再重启非persistent进程,除非用户显示调用,移除所有服务
                mService.mProcessList.removeProcessLocked(app, false, tryAgain,
                        ApplicationExitInfo.REASON_CRASH, "crash");

                //恢复最顶端的Activity
                mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
                //注意这里,该变量通常为false,所以,当频繁crash时,返回false,即不会再弹出dialog
                if (!showBackground) {
                    return false;
                }
            }
            //恢复最顶端的Activity
            mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
        } else { //相反,如果crash不频繁,结束crash activity
            final int affectedTaskId = mService.mAtmInternal.finishTopCrashedActivities(
                            app.getWindowProcessController(), reason);
            if (data != null) {
                data.taskId = affectedTaskId;
            }
            if (data != null && crashTimePersistent != null
                    && now < crashTimePersistent + ProcessList.MIN_CRASH_INTERVAL) {
                data.repeating = true;
            }
        }

        if (data != null && tryAgain) {
            data.isRestartableForService = true;
        }

        //当桌面应用crash,并且被第三方app取代,那么需要清空桌面应用的偏爱选项
        final WindowProcessController proc = app.getWindowProcessController();
        final WindowProcessController homeProc = mService.mAtmInternal.getHomeProcess();
        if (proc == homeProc && proc.hasActivities()
                && (((ProcessRecord) homeProc.mOwner).info.flags & FLAG_SYSTEM) == 0) {
            proc.clearPackagePreferredForHomeActivities();
        }

        if (!app.isolated) {
            // 无法记录孤立进程的crash时间点,由于他们并没有一个固定身份
            mProcessCrashTimes.put(app.info.processName, app.uid, now);
            mProcessCrashTimesPersistent.put(app.info.processName, app.uid, now);
        }

        //当app存在crash的handler,交给其处理
        if (app.crashHandler != null) mService.mHandler.post(app.crashHandler);
        return true;
    }

6. SHOW_ERROR_UI_MSG 消息

makeAppCrashingLocked() 返回true 时,会通过 AMS.mUiHandler发送 SHOW_ERROR_UI_MSG 消息:

frameworks/base/services/core/java/com/android/server/am/AMS.java

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_ERROR_UI_MSG: {
                    mAppErrors.handleShowAppErrorUi(msg);
                    ensureBootCompleted();
                } break;

最终是调用 mAppErrors.handleShowAppErrorUi(),代码逻辑不是很复杂,这里暂时不做剖析。

正常情况在发生 crash 时,默认系统会弹出提示 crash 的对话框,并阻塞等待用户的选择。

7. killProcess()

crash 的处理流程大致剖析完成,回到第 2 节 KillApplicationHandler.uncaughtException() 最后的finally:

frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

       public void uncaughtException(Thread t, Throwable e) {
            try {
                ...
            } catch (Throwable t2) {
                ...
            } finally {
                //确保当前进程彻底杀掉
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

至此,java crash 处理流程大致剖析完成,大致流程图总结如下:

 其中最核心的是 handleAppCrashLocked():

  • 当统一进程 1 分钟之内连续两次 crash,则执行:
    • mService.mAtmInternal.onHandleAppCrash()        
    • 对于非 persistent 进程:
      • mService.mProcessList.removeProcessLocked()
      • mService.mAtmInternal.resumeTopActivities()
    • 对于 persistent 进程:
      • mService.mAtmInternal.resumeTopActivities()
  • 没有在1分钟频繁 crash,则执行:
    • mService.mAtmInternal.finishTopCrashedActivities()

8. 避开uncaught exception

java 端提供了一个接口:

libcore/ojluni/src/main/java/java/lang/Thread.java

    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

在app 出现 crash 的时候,需要确定当线程中是否设置了 uncaughtExceptionHandler,那么原来的 defaultUncaughtExceptionHandler 将不会被调用,即如果设置了 uncaughtExceptionHandler,最终调用的是 uncaughtExceptionHandler.uncaughtException()。

利用这种方式可以避开 uncaught exception 而引起的 app 被kill。例如:

        if (true) {
            //create thread
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    String str = null;
                    System.out.println(str.length());
                }
            });
            thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
                    
                }
            });
            thread.start();
            return;
        }

上面是个简单的示例,只要通过 setUncaughtExceptionHandler() 设置一个新的UncaughtExceptionHandler, 就可以避开上述问题。

猜你喜欢

转载自blog.csdn.net/jingerppp/article/details/130969489