当前位置: 首页 > news >正文

Android Framework AMS(17)APP 异常Crash处理流程解读

该系列文章总纲链接:专题总纲目录 Android Framework 总纲


本章关键点总结 & 说明:

说明:本章节主要解读APP Crash处理。关注思维导图中左上侧部分即可。

本章节主要是对Android的APP Crash处理有一个基本的了解。从进程启动到UncaughtHandler处理方法的注册到UncaughtHandler方法异常处理、AMS的binderDied讣告流程分析。以便于我们更好地理解APP 异常处理的闭环流程。

1 从进程启动到UncaughtHandler处理方法的注册

我们需要对开机启动一个进程有一个了解,相关参考文章如下:

android 开机启动流程分析(11)Zygote启动分析

android 开机启动流程分析(13)Zygote的分裂

基于以上文章的内容解读,在Zygote分裂时,最终会执行到runOnce方法,关键代码如下:

//ZygoteConnectionboolean runOnce() throws ZygoteInit.MethodAndArgsCaller {// ...//fork进程操作pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,parsedArgs.appDataDir);//...try {if (pid == 0) {// 在子进程中执行IoUtils.closeQuietly(serverPipeFd); // 关闭服务器端文件描述符serverPipeFd = null; // 清除文件描述符引用handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr); // 处理子进程// 这里不应该到达,因为子进程应该要么抛出ZygoteInit.MethodAndArgsCaller异常,要么执行exec()。return true;} else {// 在父进程中执行...pid小于0表示失败IoUtils.closeQuietly(childPipeFd); // 关闭客户端文件描述符childPipeFd = null; // 清除文件描述符引用return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs); // 处理父进程}} finally {IoUtils.closeQuietly(childPipeFd); // 最终关闭客户端文件描述符IoUtils.closeQuietly(serverPipeFd); // 最终关闭服务器端文件描述符}}

在fork进程后,fokr出来的子进程会调用handleChildProc方法,我们主要关注该方法,代码实现如下:

//ZygoteConnectionprivate void handleChildProc(Arguments parsedArgs,FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)throws ZygoteInit.MethodAndArgsCaller {// 关闭socket连接closeSocket();// 关闭Zygote服务器的socketZygoteInit.closeServerSocket();// 如果提供了文件描述符数组if (descriptors != null) {try {// 重定向标准输入、输出和错误流ZygoteInit.reopenStdio(descriptors[0], descriptors[1], descriptors[2]);// 关闭文件描述符数组中的所有文件描述符for (FileDescriptor fd : descriptors) {IoUtils.closeQuietly(fd);}// 设置新的标准错误流newStderr = System.err;} catch (IOException ex) {// 记录重定向标准IO时的错误Log.e(TAG, "Error reopening stdio", ex);}}// 如果提供了nice名称(进程名),设置进程名if (parsedArgs.niceName != null) {Process.setArgV0(parsedArgs.niceName);}// 如果需要运行时初始化if (parsedArgs.runtimeInit) {// 如果提供了invokeWith(启动命令),使用WrapperInit启动应用程序if (parsedArgs.invokeWith != null) {WrapperInit.execApplication(parsedArgs.invokeWith,parsedArgs.niceName, parsedArgs.targetSdkVersion,pipeFd, parsedArgs.remainingArgs);} else {// 否则,使用RuntimeInit启动应用程序RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,parsedArgs.remainingArgs, null /* classLoader */);}} else {// 如果不需要运行时初始化,直接启动指定的类和主方法String className;try {// 获取类名className = parsedArgs.remainingArgs[0];} catch (ArrayIndexOutOfBoundsException ex) {// 如果类名参数缺失,记录错误并退出logAndPrintError(newStderr,"Missing required class name argument", null);return;}// 准备主方法的参数String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];System.arraycopy(parsedArgs.remainingArgs, 1,mainArgs, 0, mainArgs.length);// 如果提供了invokeWith(启动命令),使用WrapperInit启动独立应用程序if (parsedArgs.invokeWith != null) {WrapperInit.execStandalone(parsedArgs.invokeWith,parsedArgs.classpath, className, mainArgs);} else {// 否则,使用ClassLoader加载类并执行主方法ClassLoader cloader;if (parsedArgs.classpath != null) {cloader = new PathClassLoader(parsedArgs.classpath,ClassLoader.getSystemClassLoader());} else {cloader = ClassLoader.getSystemClassLoader();}try {ZygoteInit.invokeStaticMain(cloader, className, mainArgs);} catch (RuntimeException ex) {// 如果启动过程中出现异常,记录错误logAndPrintError(newStderr, "Error starting.", ex);}}}}

这里如果是第一次启动APP进程,那么parsedArgs.runtimeInit的值为true,invokeWith为null,这时候会调用到RuntimeInit.zygoteInit方法,该方法实现如下:

//RuntimeInitpublic static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)throws ZygoteInit.MethodAndArgsCaller {if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");// 重定向日志流,使得应用程序的日志能够被正确地输出redirectLogStreams();// 执行公共初始化操作,这部分代码涉及到设置进程的基本信息,如进程名称等commonInit();// 调用本地方法进行Zygote初始化,这可能涉及到JNI调用,设置本地环境等nativeZygoteInit();// 执行应用程序特定的初始化操作,这部分代码会根据传入的参数和类加载器加载应用程序的类,并启动应用程序的主线程applicationInit(targetSdkVersion, argv, classLoader);}

这里因为我们主要关注未处理的异常处理,这里的commonInit关键代码实现如下:

//RuntimeInitprivate static final void commonInit() {Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());//...initialized = true;}

这里当APP抛出未处理的异常时,都是由UncaughtHandler来处理的。到此才是APP异常处理的逻辑。

注意:如果应用程序在Android中自行设定了UncaughtExceptionHandler,这将优先于系统默认的异常处理机制。因此,当应用程序发生未捕获异常时,系统框架的崩溃处理流程将不会被触发,而是执行自定义的异常处理逻辑。通过这种方式,可以实现错误日志的自动上报功能,并且在捕获异常时防止应用程序崩溃,避免显示崩溃对话框。简而言之,自定义的UncaughtExceptionHandler允许APP开发者接管异常处理,进行日志记录和稳定性维护,而不采用系统的默认崩溃响应。

2 UncaughtHandler方法异常处理解读

UncaughtHandler的代码实现如下:

//RuntimeInitprivate static class UncaughtHandler implements Thread.UncaughtExceptionHandler {// 处理未捕获异常的方法public void uncaughtException(Thread t, Throwable e) {try {// 避免重复进入——如果崩溃报告本身崩溃,避免无限循环。if (mCrashing) return;mCrashing = true;// 如果mApplicationObject为空,表示系统进程发生了致命异常if (mApplicationObject == null) {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);}// 弹出崩溃对话框,并等待它被关闭ActivityManagerNative.getDefault().handleApplicationCrash(mApplicationObject, new ApplicationErrorReport.CrashInfo(e));} catch (Throwable t2) {//...} finally {// 尝试一切办法确保进程退出。Process.killProcess(Process.myPid());System.exit(10);}}}

UncaughtHandler类是Android系统中处理未捕获异常的关键组件,它确保了在应用程序发生崩溃时能够记录错误信息、弹出崩溃对话框,并最终确保进程退出。

这里我们主要关注ActivityManagerNative.getDefault().handleApplicationCrash方法,实际上就是AMS的handleApplicationCrash方法,该方法代码实现如下:

//AMS//关键步骤:step1public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) {ProcessRecord r = findAppProcess(app, "Crash");final String processName = app == null ? "system_server": (r == null ? "unknown" : r.processName);handleApplicationCrashInner("crash", r, processName, crashInfo);}//关键步骤:step2void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,ApplicationErrorReport.CrashInfo crashInfo) {addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);crashApplication(r, crashInfo);}//关键步骤:step3private void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {long timeMillis = System.currentTimeMillis();// 获取异常的类名和消息String shortMsg = crashInfo.exceptionClassName;String longMsg = crashInfo.exceptionMessage;// 获取异常的堆栈跟踪String stackTrace = crashInfo.stackTrace;// 构造完整的错误消息if (shortMsg != null && longMsg != null) {longMsg = shortMsg + ": " + longMsg;} else if (shortMsg != null) {longMsg = shortMsg;}AppErrorResult result = new AppErrorResult();synchronized (this) {// 如果有活动控制器,尝试通知控制器应用程序崩溃if (mController != null) {try {String name = r != null ? r.processName : null;int pid = r != null ? r.pid : Binder.getCallingPid();int uid = r != null ? r.info.uid : Binder.getCallingUid();// 通知活动控制器应用程序崩溃if (!mController.appCrashed(name, pid,shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) {// 如果控制器处理了崩溃,不再继续return;}} catch (RemoteException e) {mController = null;Watchdog.getInstance().setActivityController(null);}}// 清除调用者身份,以便执行以下操作final long origId = Binder.clearCallingIdentity();//...// 尝试使应用程序崩溃if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace)) {Binder.restoreCallingIdentity(origId);return;}// 准备显示错误消息Message msg = Message.obtain();msg.what = SHOW_ERROR_MSG;HashMap data = new HashMap();data.put("result", result);data.put("app", r);msg.obj = data;mHandler.sendMessage(msg);Binder.restoreCallingIdentity(origId);}// 获取错误处理结果int res = result.get();Intent appErrorIntent = null;synchronized (this) {// 记录崩溃次数if (r != null && !r.isolated) {mProcessCrashTimes.put(r.info.processName, r.uid,SystemClock.uptimeMillis());}// 如果结果为强制退出并报告,则创建错误 appErrorIntentif (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);}}// 如果有 appErrorIntent,尝试启动错误报告activityif (appErrorIntent != null) {try {mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));} catch (ActivityNotFoundException e) {Slog.w(TAG, "bug report receiver dissappeared", e);}}}

crashApplication方法是Android系统中处理应用程序崩溃的关键方法。它负责记录崩溃信息、通知系统监控服务、显示崩溃对话框,并最终确保应用程序正确地退出。这里我们关注尝试使应用崩溃的makeAppCrashingLocked方法逻辑和SHOW_ERROR_MSG发送后的消息处理逻辑。

2.1 makeAppCrashingLocked方法及相关流程解读

我们先来看使应用崩溃的makeAppCrashingLocked方法的实现,代码实现如下:

//AMSprivate boolean makeAppCrashingLocked(ProcessRecord app,String shortMsg, String longMsg, String stackTrace) {// 标记应用程序为崩溃状态app.crashing = true;// 生成进程错误报告app.crashingReport = generateProcessError(app,ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);// 启动应用程序问题处理流程startAppProblemLocked(app);// 停止所有冻结操作,以便应用程序可以响应崩溃app.stopFreezingAllLocked();// 处理应用程序崩溃的锁定逻辑return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace);}

makeAppCrashingLocked方法用于在应用程序崩溃时更新其状态、生成错误报告、启动问题处理流程,并确保应用程序可以正确响应崩溃事件。这里我们关注handleAppCrashLocked方法的实现,代码如下:

//AMSprivate boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg,String stackTrace) {long now = SystemClock.uptimeMillis(); // 获取当前时间Long crashTime; // 上次崩溃时间if (!app.isolated) { // 如果进程不是隔离的crashTime = mProcessCrashTimes.get(app.info.processName, app.uid); // 获取该进程上次崩溃的时间} else {crashTime = null; // 隔离进程没有崩溃时间记录}// 如果进程在短时间内频繁崩溃if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) {mStackSupervisor.handleAppCrashLocked(app); // 处理应用程序崩溃if (!app.persistent) { // 如果进程不是持久的if (!app.isolated) { // 如果进程不是隔离的// 记录不良进程信息mBadProcesses.put(app.info.processName, app.uid,new BadProcessInfo(now, shortMsg, longMsg, stackTrace));// 清除进程的崩溃时间记录mProcessCrashTimes.remove(app.info.processName, app.uid);}app.bad = true; // 标记进程为不良app.removed = true; // 标记进程为已移除removeProcessLocked(app, false, false, "crash"); // 移除进程mStackSupervisor.resumeTopActivitiesLocked(); // 恢复顶部activityreturn false; // 返回false,表示进程已被处理}mStackSupervisor.resumeTopActivitiesLocked(); // 恢复顶部activity} else {mStackSupervisor.finishTopRunningActivityLocked(app); // 完成顶部运行的activity}// 更新服务记录的崩溃次数for (int i = app.services.size() - 1; i >= 0; i--) {ServiceRecord sr = app.services.valueAt(i);sr.crashCount++;}// 如果崩溃的进程是首页进程且不是系统应用if (app == mHomeProcess && activities.size() > 0&& (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {final ActivityRecord r = activities.get(activityNdx);if (r.isHomeActivity()) { // 如果是首页activitytry {// 清除该包的首选activity设置ActivityThread.getPackageManager().clearPackagePreferredActivities(r.packageName);} catch (RemoteException c) {//...}}}}// 记录进程的崩溃时间if (!app.isolated) {mProcessCrashTimes.put(app.info.processName, app.uid, now);}// 如果进程有崩溃处理程序,发布崩溃事件if (app.crashHandler != null) mHandler.post(app.crashHandler);return true; // 返回true,表示进程崩溃事件已处理}

handleAppCrashLocked方法是Android系统中处理应用程序崩溃的关键方法。它负责记录崩溃时间、处理频繁崩溃的进程、更新服务记录、清除首页activity的首选设置,并发布崩溃事件。在处理应用程序崩溃时用到了ActivtyStackSupervisor的handleAppCrashLocked()方法,该方法代码实现如下:

//ActivtyStackSupervisorvoid handleAppCrashLocked(ProcessRecord app) {// 遍历所有显示屏幕for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {// 获取每个显示屏幕对应的activity堆栈列表final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;// 获取activity堆栈的数量final int numStacks = stacks.size();// 遍历所有activity堆栈for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {// 获取特定的activity堆栈final ActivityStack stack = stacks.get(stackNdx);// 通知activity堆栈处理应用程序崩溃stack.handleAppCrashLocked(app);}}}

该方法确保了系统能够响应应用程序崩溃事件,并在所有相关的activity堆栈中更新状态。接下来看ActivityStack的handleAppCrashLocked方法,代码实现如下所示:

//ActivtyStackvoid handleAppCrashLocked(ProcessRecord app) {// 遍历任务历史记录,从后向前遍历以处理最近的activityfor (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {// 获取每个任务中的activity列表final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;// 遍历任务中的所有activityfor (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {// 获取特定的activity记录final ActivityRecord r = activities.get(activityNdx);// 如果activity属于崩溃的应用程序if (r.app == app) {// 将activity的应用记录设置为null,表示应用已崩溃r.app = null;// 立即完成该activity,FINISH_IMMEDIATELY表示立即结束activity,不进行任何延迟finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false);}}}}

该方法用于在应用程序崩溃时更新所有属于该应用程序的activity状态。这个方法确保了系统能够响应应用程序崩溃事件,并在所有相关的activity中更新状态。接下来看对于每个满足条件的activity的最终处理方法finishCurrentActivityLocked的实现,代码如下:

//ActivtyStackfinal ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) {// 如果模式是FINISH_AFTER_VISIBLE,并且活动当前是可见的if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {// 如果活动不在停止活动中列表中,则添加到列表if (!mStackSupervisor.mStoppingActivities.contains(r)) {mStackSupervisor.mStoppingActivities.add(r);// 如果停止活动的数量超过3个,或者活动是任务的前台活动且任务历史记录数量小于等于1,则计划空闲if (mStackSupervisor.mStoppingActivities.size() > 3|| r.frontOfTask && mTaskHistory.size() <= 1) {mStackSupervisor.scheduleIdleLocked();} else {// 否则,检查是否准备好睡眠mStackSupervisor.checkReadyForSleepLocked();}}// 设置活动状态为停止中r.state = ActivityState.STOPPING;// 如果需要调整OOM值,则更新if (oomAdj) {mService.updateOomAdjLocked();}return r; // 返回活动记录}// 从各种活动中列表中移除当前活动mStackSupervisor.mStoppingActivities.remove(r);mStackSupervisor.mGoingToSleepActivities.remove(r);mStackSupervisor.mWaitingVisibleActivities.remove(r);// 如果当前活动是恢复的活动,则将其设置为nullif (mResumedActivity == r) {mResumedActivity = null;}// 记录活动之前的状态final ActivityState prevState = r.state;// 设置活动状态为完成中r.state = ActivityState.FINISHING;// 如果模式是FINISH_IMMEDIATELY,或者之前的状态是STOPPED或INITIALIZINGif (mode == FINISH_IMMEDIATELY|| prevState == ActivityState.STOPPED|| prevState == ActivityState.INITIALIZING) {// 标记活动为完成r.makeFinishing();// 销毁活动boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");// 如果活动被移除,则恢复顶部活动if (activityRemoved) {mStackSupervisor.resumeTopActivitiesLocked();}return activityRemoved ? null : r; // 返回活动记录或null}// 将活动添加到完成活动中列表mStackSupervisor.mFinishingActivities.add(r);// 恢复分发键事件r.resumeKeyDispatchingLocked();// 恢复顶部活动mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);return r; // 返回活动记录}

这段代码的处理逻辑是为了确保在活动完成时,系统能够正确地管理活动生命周期,同时保持用户界面的响应性和连续性。通过将活动添加到完成列表、恢复键事件分发和恢复顶部活动,系统可以确保在活动销毁后,用户界面能够立即响应用户的操作,并且不会留下任何悬空的状态。这种方法有助于提供平滑的用户体验,即使在活动发生崩溃或需要关闭的情况下。

2.2 SHOW_ERROR_MSG消息处理

发送SHOW_ERROR_MSG消息后,MainHandler会通过handlerMessage来处理,代码实现如下:

//AMSfinal class MainHandler extends Handler {public MainHandler(Looper looper) {super(looper, null, true);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case SHOW_ERROR_MSG: {// 从消息中获取数据HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;// 检查设置中是否允许显示后台应用的错误对话框boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;synchronized (ActivityManagerService.this) {// 从数据中获取进程记录和错误结果对象ProcessRecord proc = (ProcessRecord)data.get("app");AppErrorResult res = (AppErrorResult) data.get("result");// 如果进程已经有错误对话框,或者结果对象为空,则直接返回if (proc != null && proc.crashDialog != null) {if (res != null) {res.set(0);}return;}// 判断进程是否为后台进程boolean isBackground = (UserHandle.getAppId(proc.uid)>= Process.FIRST_APPLICATION_UID&& proc.pid != MY_PID);for (int userId : mCurrentProfileIds) {isBackground &= (proc.userId != userId);}// 如果设置中不允许显示后台应用的错误对话框,且进程为后台进程,则直接返回if (isBackground && !showBackground) {if (res != null) {res.set(0);}return;}// 如果允许显示对话框,且系统未休眠,且未关闭,则显示错误对话框if (mShowDialogs && !mSleeping && !mShuttingDown) {Dialog d = new AppErrorDialog(mContext, ActivityManagerService.this, res, proc);d.show();proc.crashDialog = d; // 将对话框保存到进程记录中} else {if (res != null) {res.set(0);}}}ensureBootCompleted(); // 确保系统启动完成} break;// ...}}//...}

这段代码处理应用程序错误对话框的显示逻辑,包括检查系统设置、判断进程是否为后台进程、创建和显示错误对话框等。这里最将异常错误相关信息呈现在用户的眼前。到这里 APP Crash的相关逻辑处理就结束了。但是因为应用退出导致它的讣告接收对象被唤醒,也就是会通知AMS,走到对应的binderDied的流程。

最后总结下,关键方法handleApplicationCrash及这条线的逻辑。为了确保当Android系统中的应用程序进程发生崩溃时,系统能够以一种可控和用户友好的方式进行响应和处理。以下是该方法设计的几个关键意义:

  • 维护系统稳定性:通过及时响应应用程序崩溃,handleApplicationCrash方法有助于防止系统级故障,确保整个系统的稳定性和可靠性。
  • 提供用户反馈:该方法通常会显示一个崩溃对话框(crash dialog),向用户报告应用程序崩溃的情况,提供用户友好的错误信息和可能的解决方案。
  • 资源回收和释放:确保崩溃的应用程序进程所占用的资源得到正确回收和释放,避免资源泄露,如内存、文件描述符和数据库连接等。
  • 应用程序重启:对于某些关键的应用程序,系统可能需要自动重启崩溃的进程,以尽量恢复服务,减少对用户体验的影响。
  • 错误报告和日志记录:收集崩溃相关的信息,如异常堆栈、错误日志等,这对于后续的错误分析、调试和修复至关重要。
  • 保护用户数据:在应用程序崩溃时,确保用户数据不受损失,或者提示用户保存工作,以防止数据丢失。
  • 系统监控和性能优化:通过监控应用程序的崩溃情况,系统可以识别潜在的性能问题和系统瓶颈,为性能优化提供数据支持。
  • 安全性考虑:崩溃处理机制还包括安全性考虑,确保应用程序崩溃不会导致系统安全漏洞或敏感信息泄露。
  • 应用程序生命周期管理:该方法有助于维护应用程序的正确生命周期,确保应用程序在崩溃后能够正确地结束或重启,符合其生命周期规范。
  • 用户体验连续性:通过适当的崩溃处理,系统可以尽量保持用户体验的连续性,尤其是在前台应用程序崩溃时,系统可以尽快恢复用户的操作环境。

综上所述,handleApplicationCrash方法的设计意义在于提供一个全面的崩溃处理机制,以保护用户利益、维护系统稳定性、优化性能,并为开发者提供必要的错误信息,以便快速定位和解决问题。

3 AMS的binderDied讣告流程分析

3.1 binderDied讣告被调用的流程

因为应用退出导致它的讣告接收对象被唤醒,也就是会走到binderDied的流程,关于具体如何让走到讣告的,可以参考文章:

android 系统核心机制binder(07)binder挂掉客户端收到通知

也就是说当APP进程挂掉时AMS会收到讣告,可是到底是什么讣告呢?这里以startProcessLocked方法启动一个新进程为例,启动相关的代码可参考文章:

Android Framework AMS(04)startActivity分析-1(am启动到ActivityThread启动)

Android Framework AMS(05)startActivity分析-2(ActivityThread启动到Activity拉起)

这里我们主要从AMS的attachApplicationLocked方法入手进行分析。代码实现如下:

    private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {//...try {// 创建应用程序死亡接收器AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread);// 将死亡接收器与应用程序线程的Binder关联thread.asBinder().linkToDeath(adr, 0);// 将死亡接收器设置到应用程序记录中app.deathRecipient = adr;} catch (RemoteException e) {// 如果发生远程异常,重置应用程序的包列表app.resetPackageList(mProcessStats);// 重新启动应用程序进程startProcessLocked(app, "link fail", processName);return false; // 返回false,表示关联失败}return true;}

attachApplicationLocked方法的主要目的是确保AMS能够监控应用程序进程的生命周期。通过设置死亡接收器,当应用程序进程意外死亡时,系统能够及时响应并采取相应的措施,如重启进程或清理资源。这里的讣告类型是AppDeathRecipient,因此接下来我们继续分析AppDeathRecipient中的binderDied相关流程。

3.2讣告处理流程

AppDeathRecipient(包含binderDied)的完整代码如下所示:

//AMSprivate final class AppDeathRecipient implements IBinder.DeathRecipient {final ProcessRecord mApp;final int mPid;final IApplicationThread mAppThread;AppDeathRecipient(ProcessRecord app, int pid,IApplicationThread thread) {mApp = app;mPid = pid;mAppThread = thread;}//关键方法@Overridepublic void binderDied() {synchronized(ActivityManagerService.this) {appDiedLocked(mApp, mPid, mAppThread);}}}

当APP进程挂掉时会调用到binderDied的方法,binderDied实现很简单,就是调用appDiedLocked方法,基于这个方法继续分析,该方法代码实现如下:

//AMSfinal void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) {// 同步mPidsSelfLocked,确保mPidsSelfLocked的一致性synchronized (mPidsSelfLocked) {// 获取mPidsSelfLocked中对应pid的ProcessRecordProcessRecord curProc = mPidsSelfLocked.get(pid);// 如果当前pid对应的ProcessRecord不是我们关心的app,直接返回if (curProc != app) {return;}}// 获取活跃的电池统计服务BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();// 同步stats,确保电池统计的一致性synchronized (stats) {// 记录进程死亡事件stats.noteProcessDiedLocked(app.info.uid, pid);}// 如果app还没有被标记为已杀死if (!app.killed) {// 静默杀死进程Process.killProcessQuiet(pid);// 杀死进程组Process.killProcessGroup(app.info.uid, pid);// 标记app为已杀死app.killed = true;}// 如果app的pid等于传入的pid,并且app的thread不为空,且thread的binder与传入的thread相同if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) {// 如果app没有instrumentation类,则需要进行低内存报告boolean doLowMem = app.instrumentationClass == null;// 是否需要更新OOM调整值boolean doOomAdj = doLowMem;// 如果app不是被ActivityManager杀死的if (!app.killedByAm) {// 允许降低内存级别mAllowLowerMemLevel = true;} else {// 不允许降低内存级别mAllowLowerMemLevel = false;// 不需要进行低内存报告doLowMem = false;}// 处理app死亡handleAppDiedLocked(app, false, true);// 如果需要更新OOM调整值,则更新if (doOomAdj) {updateOomAdjLocked();}// 如果需要进行低内存报告,则进行if (doLowMem) {doLowMemReportIfNeededLocked(app);}}//...}

appDiedLocked方法是Android系统中处理应用程序进程死亡的关键方法。它负责更新电池统计、杀死进程、调整OOM调整值和进行低内存报告。这里我们关注handleAppDiedLocked这个关键方法,代码实现如下:

private final void handleAppDiedLocked(ProcessRecord app, boolean restarting, boolean allowRestart) {int pid = app.pid; // 获取进程ID// 清理应用程序记录,返回是否保留该进程记录boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);// 如果不保留且不是重启中if (!kept && !restarting) {// 从LRU列表中移除进程removeLruProcessLocked(app);// 如果进程ID大于0,则从进程列表中移除if (pid > 0) {ProcessList.remove(pid);}}// 如果当前进程是正在被分析的进程if (mProfileProc == app) {// 清除分析器clearProfilerLocked();}// 处理应用程序死亡,返回是否有可见activityboolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);// 清除应用程序的所有activityapp.activities.clear();// 如果进程有测试框架类if (app.instrumentationClass != null) {// 创建一个Bundle,用于传递测试结果Bundle info = new Bundle();info.putString("shortMsg", "Process crashed.");// 完成测试框架finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info);}// 如果不是重启中if (!restarting) {// 尝试恢复顶部activityif (!mStackSupervisor.resumeTopActivitiesLocked()) {// 如果有可见activity,确保activity可见if (hasVisibleActivities) {mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);}}}}

handleAppDiedLocked方法负责清理应用程序记录、移除进程、清除分析器、处理应用程序死亡、清除应用程序的所有活动、完成测试仪器以及恢复顶部活动。通过这种方式,Android系统能够响应应用程序进程的死亡事件,并进行适当的资源回收和状态更新。

这里我们主要关注cleanUpApplicationRecordLocked方法,它负责清理应用程序记录、移除进程、清理对话框、重置状态、杀死服务、移除ContentProvider、跳过广播接收者等,可以说涉及4大组件的收为处理工作。代码实现如下:

//AMSprivate final boolean cleanUpApplicationRecordLocked(ProcessRecord app,boolean restarting, boolean allowRestart, int index) {// 如果提供了有效的索引,从LRU列表和进程列表中移除进程if (index >= 0) {removeLruProcessLocked(app);ProcessList.remove(app.pid);}// 从待GC列表和待PSS列表中移除进程mProcessesToGc.remove(app);mPendingPssProcesses.remove(app);// 关闭所有打开的对话框if (app.crashDialog != null && !app.forceCrashReport) {app.crashDialog.dismiss();app.crashDialog = null;}if (app.anrDialog != null) {app.anrDialog.dismiss();app.anrDialog = null;}if (app.waitDialog != null) {app.waitDialog.dismiss();app.waitDialog = null;}// 重置进程状态app.crashing = false;app.notResponding = false;// 重置进程包列表app.resetPackageList(mProcessStats);// 移除死亡接收者app.unlinkDeathRecipient();// 使进程变为非活动状态app.makeInactive(mProcessStats);// 清理进程等待杀死和强制前台的引用app.waitingToKill = null;app.forcingToForeground = null;// 更新进程前台状态updateProcessForegroundLocked(app, false, false);app.foregroundActivities = false;app.hasShownUi = false;app.treatLikeActivity = false;app.hasAboveClient = false;app.hasClientActivities = false;// 杀死进程中的服务mServices.killServicesLocked(app, allowRestart);boolean restart = false;// 移除发布的ContentProviderfor (int i = app.pubProviders.size() - 1; i >= 0; i--) {ContentProviderRecord cpr = app.pubProviders.valueAt(i);final boolean always = app.bad || !allowRestart;// 如果需要重启ContentProvider或进程已坏if (removeDyingProviderLocked(app, cpr, always) || always) {restart = true;}cpr.provider = null;cpr.proc = null;}app.pubProviders.clear();if (checkAppInLaunchingProvidersLocked(app, false)) {restart = true;}// 清理连接的ContentProviderif (!app.conProviders.isEmpty()) {for (int i = 0; i < app.conProviders.size(); i++) {ContentProviderConnection conn = app.conProviders.get(i);conn.provider.connections.remove(conn);stopAssociationLocked(app.uid, app.processName, conn.provider.uid,conn.provider.name);}app.conProviders.clear();}// 跳过当前的广播接收者skipCurrentReceiverLocked(app);// 移除所有的广播接收者for (int i = app.receivers.size() - 1; i >= 0; i--) {removeReceiverLocked(app.receivers.valueAt(i));}app.receivers.clear();// 如果备份目标进程死亡,通知备份管理器if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) {try {IBackupManager bm = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));bm.agentDisconnected(app.info.packageName);} catch (RemoteException e) {// ...}}// 处理挂起的进程变化for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {ProcessChangeItem item = mPendingProcessChanges.get(i);if (item.pid == app.pid) {mPendingProcessChanges.remove(i);mAvailProcessChanges.add(item);}}// 发送进程死亡的消息mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();// 如果进程正在重启,返回falseif (restarting) {return false;}// 清理持久进程和隔离进程的记录if (!app.persistent || app.isolated) {mProcessNames.remove(app.processName, app.uid);mIsolatedProcesses.remove(app.uid);if (mHeavyWeightProcess == app) {mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,mHeavyWeightProcess.userId, 0));mHeavyWeightProcess = null;}} else if (!app.removed) {// 如果持久进程没有在启动列表中,添加到启动列表并标记需要重启if (mPersistentStartingProcesses.indexOf(app) < 0) {mPersistentStartingProcesses.add(app);restart = true;}}mProcessesOnHold.remove(app);// 清理首页进程和前一个进程的记录if (app == mHomeProcess) {mHomeProcess = null;}if (app == mPreviousProcess) {mPreviousProcess = null;}// 如果需要重启且进程不是隔离的,重启进程if (restart && !app.isolated) {if (index < 0) {ProcessList.remove(app.pid);}mProcessNames.put(app.processName, app.uid, app);startProcessLocked(app, "restart", app.processName);return true;} else if (app.pid > 0 && app.pid != MY_PID) {// 从mPidsSelfLocked中移除进程boolean removed;synchronized (mPidsSelfLocked) {mPidsSelfLocked.remove(app.pid);mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);}// 通知电池统计服务进程结束mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);if (app.isolated) {mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);}app.setPid(0);}return false;}

cleanUpApplicationRecordLocked方法的设计目的是为了在应用程序进程死亡时,对Android系统中的四大组件(Activity、Service、ContentProvider、BroadcastReceiver)以及其它相关资源进行统一的管理和清理。以下是该方法对四大组件的处理方式:

对Activity的处理:

  • 清理Activity记录:当应用程序进程死亡时,该方法会清理所有与该进程相关联的Activity记录,包括从ActivityStack中移除这些Activity。
  • 状态更新:更新Activity的状态,将它们标记为已销毁(ActivityState.DESTROYED),确保系统不会尝试与这些Activity进行交互。
  • 移除历史记录:从各种Activity历史记录列表中移除相关Activity,如mLRUActivitiesmStoppingActivities等。

对Service的处理:

  • 杀死服务:该方法会杀死与该应用程序进程相关联的所有服务。
  • 清理服务连接:清理与服务的所有连接,包括移除服务的绑定和停止服务。

对ContentProvider的处理:

  • 移除ContentProvider:移除应用程序进程发布的所有ContentProvider,并清理相关记录。
  • 清理连接的ContentProvider:清理应用程序进程连接的所有ContentProvider。

对BroadcastReceiver的处理:

  • 跳过当前的广播接收者:如果进程中有正在处理的广播接收者,该方法会跳过当前的广播接收者。
  • 移除广播接收者:从广播接收者列表中移除所有与该应用程序进程相关联的BroadcastReceiver。

除了4大组件的处理还有一些其他处理,总结如下:

  • 资源和内存管理:清理与应用程序进程相关的所有资源,包括文件描述符、网络连接等,以确保资源不会泄露。
  • 进程重启决策:根据应用程序的配置和系统策略,决定是否需要重启该应用程序进程。
  • 用户体验维护:通过清理和可能的重启操作,最小化应用程序崩溃对用户体验的影响。

总结来说,cleanUpApplicationRecordLocked方法的设计目的是确保在应用程序进程死亡时,系统能够统一管理和清理与该进程相关的所有组件和资源,以维护系统的稳定性和响应性。

最后总结下appDiedLocked方法这条线的逻辑。该方法的主要设计目的在于确保当一个应用程序进程意外崩溃或被终止时,Android系统能够进行适当的清理和后续处理。以下是该方法实现的几个关键目标:

  • 进程状态同步:确保系统准确记录进程的死亡状态,更新进程的生命周期状态,以便系统不再将资源或事件分配给已不存在的进程。
  • 资源回收:释放与已死亡进程相关联的所有资源,包括内存、文件描述符、数据库连接等,防止资源泄露。
  • 用户体验保护:通过重启崩溃的应用程序进程(如果配置允许)或清理用户界面,确保用户不会遇到应用程序挂起或无响应的情况。
  • 系统稳定性维护:及时处理进程死亡事件,避免系统其他部分因等待已不存在的进程响应而出现稳定性问题。
  • 服务和内容提供者管理:清理进程中运行的所有服务和内容提供者,确保数据一致性,并释放相关资源。
  • 广播接收器处理:管理与进程相关的广播接收器,确保系统不会向已死亡的进程发送广播,避免潜在的系统崩溃。
  • 重启策略执行:对于前台进程或关键服务,根据需要执行重启策略,以保证应用程序的连续性和服务的可用性。
  • 系统监控和错误报告:收集崩溃进程的信息,为系统监控、错误报告和后续的调试提供数据支持。
  • 用户界面更新:如果进程包含用户可见的活动,确保用户界面得到更新,如显示崩溃对话框或重启活动。
  • 系统性能优化:通过清理不再需要的进程记录和资源,优化系统性能,提高剩余进程的响应速度和效率。

appDiedLocked方法的设计目的是在应用程序进程死亡时,进行系统级的清理和恢复操作,以保护用户体验,维护系统稳定性,并确保资源得到合理管理。


http://www.mrgr.cn/news/75227.html

相关文章:

  • AI制作ppt
  • 设计模式之责任链模式(Chain Of Responsibility)
  • 【activiti工作流源码集成】springboot+activiti+mysql+vue+redis工作流审批流集成整合业务绑定表单流程图会签驳回
  • 基于Python+Django+Vue3+MySQL实现的前后端分类的商场车辆管理系统
  • ECharts 实现大屏地图功能
  • 掌握核密度图:精准描绘不同年龄段的血糖分布
  • 教你使用 Lisp 编写 ChatGPT 对话机器人
  • 解决 Mybatis-Plus 中 `updateById` 方法不更新空值、更新字段无效的问题
  • Altium Designer使用技巧(五)
  • 微服务day08
  • AUTOSAR_EXP_ARAComAPI的7章笔记(3)
  • 17-鸿蒙开发中的背景图片设置:位置、定位、单位和尺寸
  • Linux软件包管理与Vim编辑器使用指南
  • 绝对路径和相对路径的区别
  • 搜维尔科技:我们使用Xsens动作捕捉技术创建的短片
  • 行驶证 OCR 识别 API 接口的优势分析
  • Python中,处理日期和时间的库
  • GCN基于图卷积神经网络多特征分类预测(多输入单输出) Matlab代码
  • springboot039基于Web足球青训俱乐部管理系统
  • 似然函数解析
  • LeetCode 每日一题 统计满足 K 约束的子字符串数量 I
  • AI视觉小车基础--2.按键读取
  • 【MYSQL】数据库日志 (了解即可)
  • Linux 驱动
  • 机器学习(1)
  • [DB]