Android crash 流程详解(一):JE

news2024/11/15 11:27:18

源码基于: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 文件名为 system_server_crash@xxx.txt (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, 就可以避开上述问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/594348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Shell脚本攻略:shell函数应用

目录 一、理论 1.shell函数 2.函数传参 3.函数变量的作用范围 4.递归 5.函数位置变量与脚本位置变量区别 6.创建库 二、实验 1.实验一 一、理论 1.shell函数 &#xff08;1&#xff09;概念 将命令序列按格式写在一起&#xff0c;可方便重复使用命令序列。 ① 避免…

JetBrains的Java集成开发环境IntelliJ 2023版本在Win10系统的下载与安装配置教程

目录 前言一、IntelliJ 安装二、使用配置总结 前言 IntelliJ IDEA Ultimate是一款功能强大的Java集成开发环境&#xff08;IDE&#xff09;。它提供了丰富的功能和工具&#xff0c;可以帮助开发人员更高效地编写、调试和部署Java应用程序。 IntelliJ IDEA Ultimate的主要特点…

Benewake(北醒) 快速实现TFmini-Plus-IIC与电脑通信的操作说明

目录 1. 概述2. 测试准备2.1 工具准备2.2通讯协议转换 3. IIC通讯测试3.1 引脚说明3.2 测试步骤3.2.1 TFmini-Plus-IIC 与 PC 建立连接3.2.2 获取测距值3.2.3 更改 slave 地址 1. 概述 通过本文档的概述&#xff0c;能够让初次使用测试者快速了解测试 IIC 通信协议需要的工具以…

48. 旋转图像

48. 旋转图像 C代码&#xff1a; void rotate(int** matrix, int matrixSize, int* matrixColSize){int m matrixSize;int n matrixColSize[0];int arr[m*n];int arrTop 0;memset(arr, 0, sizeof(int) * m * n);for (int i 0; i < m; i) {for (int j 0; j < n; j) …

【Springcloud】分布式搜索elasticsearch

文章目录 1、ElasticSearch 1、ElasticSearch 先看下翻译&#xff1a; elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 项目在运行的时候会产生海量的日志信息&#xff0c;而elasticsearch结合kibana、Logstash、Beats&am…

2023免费的苹果手机备份app软件iMazing

苹果备份APP怎么备份&#xff1f;一般情况下&#xff0c;苹果手机备份照片、短信等可以使用iCloud备份。虽然App也可以使用iCloud备份&#xff0c;但是App数据一般较多&#xff0c;需要较大的iCloud存储空间&#xff0c;而免费的iCloud存储空间只有5GB&#xff0c;很多人都不想…

[C++] 继承和多态

Be water my friend. 一.关于继承(inheritance) 基础知识&#xff1a; 继承的定义格式&#xff1a; 继承方式的比较&#xff1a; 继承中的作用域&#xff1a; 基类和派生类对象赋值转换 : 派生类的默认成员函数 关于继承的补充 如何防止继承的发生(final关键字…

ThreeJS教程:CSS3批量标注多个标签

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 3D工具集&#xff1a; NSDT简石数字孪生 CSS3批量标注多个标签 下面下工厂为例&#xff0c;使用CSS3DRenderer批量渲染多个HTML元素标签。 CSS3渲染器基本代码 CSS3渲染器代码和上节课内容一样设置即可。 // 引入CSS3渲…

掌握哪些测试技术才能说自己已经学成了?

一、过硬的基础能力 其实所有的测试大佬都是从底层基础开始的&#xff0c;随着时间&#xff0c;经验的积累慢慢变成大佬。要想稳扎稳打在测试行业深耕&#xff0c;成为测试大牛&#xff0c;首当其冲的肯定就是拥有过硬的基础&#xff0c;所有的基础都是根基&#xff0c;后期所…

Docker利用DockerFile创建部署NVIDIA+PyTorch容器

Docker利用DockerFile创建部署NVIDIAPyTorch容器 1、创建 Dockerfile2、在 Dockerfile 中添加关键字和命令3、使用 Docker Build 命令构建镜像4、验证和测试 Docker 映像 1、创建 Dockerfile 首先在用户的主目录下创建一个名为 mycode 的文件夹&#xff0c;然后创建 Dockerfil…

马斯克:我是 Rust 粉丝,但为了性能我会毫不犹豫选择 C/C++

作为一个几乎时刻处于风口浪尖上的“网络红人”&#xff0c;特斯拉 CEO 埃隆马斯克(Elon Musk)被外界评价为“致力于从人工智能手中拯救人类”的钢铁侠。近期&#xff0c;这位大佬又因不少“出格”言论而在社交媒体上引发热议 —— 在一家 AI 公司谈论编程“error messages”的…

【备战秋招】每日一题:4月1日美团春招(二批)第四题:题面+题目思路 + C++/python/js/Go/java带注释

2023大厂笔试模拟练习网站&#xff08;含题解&#xff09; www.codefun2000.com 最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据&#xff0c;挂载到我们的OJ上&#xff0c;供大家学习交流&#xff0c;体会笔试难度。现已录入200道互联网大厂模拟练习题&…

电商 - 高并发下订单商品库存扣减方案

开发一个电商库存系统时,我们最担心的就是高并发和防超卖了 电商库存系统场景 前提:分布式系统,高并发 商品A只有100库存,现在有1000或者更多的用户购买。如何保证商品库存在高并发的场景下是安全的 高并发场景下,商品展示页上面的信息,除了库存的其他信息属于静态数据…

ClickHouse性能调优——压缩和编码算法

随着数据库数据越来越多&#xff0c;给数据存储、网络访问造成成本和负担。压缩技术节约存储空间、加速网络访问的常用解决方案&#xff0c;本文主要介绍压缩算法和ClickHouse编码技术。 压缩类型 ClickHouse协议支持LZ4和ZSTD 压缩算法&#xff0c;两者都是基于字典使用校验和…

【Linux】信号(一文学会,八千字好文深度讲解信号)

目录 1.信号的初步理解 2.信号处理 信号的产生 信号的保存 前台进程和后台进程 信号处理以及产生信号 对于信号的处理方式有三种 产生信号&#xff1a; 1.通过终端按键产生信号 2.调用系统函数向进程发信号​编辑 ​编辑 3. 由软件条件产生信号 4.硬件异常产生信…

docker私有仓库harbor部署

1. harbor简介&#xff1a; Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器&#xff0c;通过添加一些企业必需的功能特性&#xff0c;例如安全、标识和管理等&#xff0c;扩展了开源Docker Distribution。作为一个企业级私有Registry服务器&#xff0c;Harbor提…

系列二、MongoDB的安装

一、传统方式安装 1.1、下载安装包 https://www.mongodb.com/try/download/community-kubernetes-operator 1.2、上传至opt目录并解压 tzr -xzvf mongodb-linux-x86_64-rhel70-5.0.18.tgz 1.3、移动mongodb安装包并重命名 mv mongodb-linux-x86_64-rhel70-5.0.18 /usr/local…

【Unity100个实用小技巧】屏幕后处理实现渐隐渐现

☀️博客主页&#xff1a;CSDN博客主页&#x1f4a8;本文由 我是小狼君 原创&#xff0c;首发于 CSDN&#x1f4a2;&#x1f525;学习专栏推荐&#xff1a;面试汇总❗️游戏框架专栏推荐&#xff1a;游戏实用框架专栏⛅️点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd;&#xf…

35 KVM管理设备-管理虚拟网卡

文章目录 35 KVM管理设备-管理虚拟网卡35.1 概述35.2 操作步骤 35 KVM管理设备-管理虚拟网卡 35.1 概述 虚拟网卡类型主要包含virtio-net、vhost-net、vhost-user等。用户在创建虚拟机后&#xff0c;可能会有挂载或者卸载虚拟网卡的需求。openEuler提供了网卡热插拔的功能&am…

springboot+vue宠物领养系统的设计与实现

随着国内经济的不断发展&#xff0c;人民收入水平的提高以及对于情感需求的日益增强&#xff0c;宠物饲养成为了一种流行趋势。宠物的增多不可避免地造成了流浪宠物的泛滥&#xff0c;它们大多来自被主人遗弃的动物或这些动物繁衍的后代。它们没有管束&#xff0c;游走在人类居…