Android 7.0 ActivityManagerService(10) App的crash处理
来源:互联网 发布:淘宝客引流方法 编辑:程序博客网 时间:2024/06/07 04:46
在这篇博客中,我们来看一下AMS处理App crash时涉及到的主要流程。
一、设置异常处理器
在Android平台中,应用进程fork出来后会为虚拟机设置一个未截获异常处理器,
即在程序运行时,如果有任何一个线程抛出了未被截获的异常,
那么该异常最终会抛给未截获异常处理器处理。
我们首先看看Android N中设置异常处理器的这部分代码。
在ZygoteInit.Java的runSelectLoop中:
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller { ........... while (true) { .......... for (int i = pollFds.length - 1; i >= 0; --i) { .......... if (i == 0) { //zygote中的server socket收到消息后,建立起ZygoteConnection ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { //ZygoteConnection建立后,收到消息调用自己的runOnce函数 boolean done = peers.get(i).runOnce(); ............. } } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
我们知道zygote启动后,会在自己的进程中定义一个server socket,专门接收创建进程的消息。
如上面的代码所示,收到创建进程的消息后,zygote会创建出ZygoteConnection,并调用其runOnce函数:
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller { ............... try { ........... //fork出子进程 pid = Zygote.forkAndSpecialize(.......); } catch (ErrnoException ex) { ............ } catch (IllegalArgumentException ex) { ........... } catch (ZygoteSecurityException ex) { .......... } try { if (pid == 0) { ........ //进程fork成功后,进行处理 handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr); ........ } else { ........... } } finally { .......... }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
我们跟进一下handleChildProc函数:
private void handleChildProc(......) { ............ if (parsedArgs.invokeWith != null) { .......... } else { //进入到RuntimeInit中的zygoteInit函数 RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, null /* classLoader */); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
顺着流程看一看RuntimeInit中的zygoteInit函数:
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { ............ //跟进commonInit commonInit(); ............}private static final void commonInit() { ........... /* set default handler; this applies to all threads in the VM */ //到达目的地! Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler()); ...........}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
从上面的代码可以看出,fork出进程后,将在进程commonInit的阶段设置异常处理器UncaughtHandler。
二、异常处理的流程
1、UncaughtHandler的异常处理
接下来我们看看UncaughtHandler如何处理未被捕获的异常。
private static class UncaughtHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { try { // Don't re-enter -- avoid infinite loops if crash-reporting crashes. if (mCrashing) return; mCrashing = true; if (mApplicationObject == null) { Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e); } else { //打印进程的crash信息 ............. } ............. // Bring up crash dialog, wait for it to be dismissed //调用AMS的接口,进行处理 ActivityManagerNative.getDefault().handleApplicationCrash( mApplicationObject, new ApplicationErrorReport.CrashInfo(e)); } catch (Throwable t2) { if (t2 instanceof DeadObjectException) { // System process is dead; ignore } else { try { Clog_e(TAG, "Error reporting crash", t2); } catch (Throwable t3) { // Even Clog_e() fails! Oh well. } } } finally { // Try everything to make sure this process goes away. //crash的最后,会杀死进程 Process.killProcess(Process.myPid()); //并exit System.exit(10); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
从代码来看,UncaughtHandler对异常的处理流程比较清晰,基本上就是:
1、记录log信息;
2、调用AMS的接口进行一些处理;
3、杀死出现crash的进程。
其中比较重要的应该是AMS处理crash的流程,接下来我们跟进一下这部分流程的代码。
2、AMS的异常处理
public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) { //得到crash app对应的信息 ProcessRecord r = findAppProcess(app, "Crash"); final String processName = app == null ? "system_server" : (r == null ? "unknown" : r.processName); //调用handleApplicationCrashInner进一步处理 handleApplicationCrashInner("crash", r, processName, crashInfo);}void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName, ApplicationErrorReport.CrashInfo crashInfo) { ............... //Write a description of an error (crash, WTF, ANR) to the drop box. //记录信息到drop box addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo); //调用内部类AppErrors的crashApplication函数 mAppErrors.crashApplication(r, crashInfo);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
我们跟进一下AppErrors类中的crashApplication函数:
/*** Bring up the "unexpected error" dialog box for a crashing app.* Deal with edge cases (intercepts from instrumented applications,* ActivityController, error intent receivers, that sort of thing).* /void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) { final long origId = Binder.clearCallingIdentity(); try { //实际的处理函数为crashApplicationInner crashApplicationInner(r, crashInfo); } finally { Binder.restoreCallingIdentity(origId); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
此处实际的处理函数为crashApplicationInner。
void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) { long timeMillis = System.currentTimeMillis(); //从应用进程传递过来的crashInfo中获取相关的信息 String shortMsg = crashInfo.exceptionClassName; String longMsg = crashInfo.exceptionMessage; String stackTrace = crashInfo.stackTrace; ................ AppErrorResult result = new AppErrorResult(); TaskRecord task; synchronized (mService) { /** * If crash is handled by instance of {@link android.app.IActivityController}, * finish now and don't show the app error dialog. */ //通知观察者处理crash //如果存在观察者且能够处理crash,那么不显示error dialog //例如在进行Monkey Test,那么可设置检测到crash后,就停止测试等 if (handleAppCrashInActivityController(r, crashInfo, shortMsg, longMsg, stackTrace, timeMillis)) { return; } /** * If this process was running instrumentation, finish now - it will be handled in * {@link ActivityManagerService#handleAppDiedLocked}. */ if (r != null && r.instrumentationClass != null) { return; } // Log crash in battery stats. if (r != null) { mService.mBatteryStatsService.noteProcessCrash(r.processName, r.uid); } AppErrorDialog.Data data = new AppErrorDialog.Data(); data.result = result; data.proc = r; // If we can't identify the process or it's already exceeded its crash quota, // quit right away without showing a crash dialog. // 调用makeAppCrashingLocked进行处理,如果返回false,则无需进行后续处理 if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) { return; } //发送SHOW_ERROR_UI_MSG给mUiHandler,将弹出一个对话框,提示用户某进程crash //用户可以选择"退出"或"退出并报告"等 //一般的厂商应该都定制了这个界面 Message msg = Message.obtain(); msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG; task = data.task; msg.obj = data; mService.mUiHandler.sendMessage(msg); } //调用AppErrorResult的get函数,该函数是阻塞的,直到用户处理了对话框为止 //注意此处涉及了两个线程的工作 //crashApplicationInner函数工作在Binder调用所在的线程 //对话框工作于AMS的Ui线程 int res = result.get(); Intent appErrorIntent = null; //以下开始根据对话框中用户的选择,进行对应的处理 ................... //长时间未点击对话框或者点击取消,那么相当于选择强行停止crash进程 if (res == AppErrorDialog.TIMEOUT || res == AppErrorDialog.CANCEL) { res = AppErrorDialog.FORCE_QUIT; } //根据res的值进行相应的处理 synchronized (mService) { //选择不再提示错误 if (res == AppErrorDialog.MUTE) { //将进程名加入到AMS的mAppsNotReportingCrashes表中 stopReportingCrashesLocked(r); } //选择了重新启动 if (res == AppErrorDialog.RESTART) { mService.removeProcessLocked(r, false, true, "crash"); if (task != null) { try { //尝试重启进程 mService.startActivityFromRecents(task.taskId, ActivityOptions.makeBasic().toBundle()); } catch (IllegalArgumentException e) { // Hmm, that didn't work, app might have crashed before creating a // recents entry. Let's see if we have a safe-to-restart intent. if (task.intent.getCategories().contains( Intent.CATEGORY_LAUNCHER)) { //换一种方式重启 mService.startActivityInPackage(............); } } } } //选择强行停止 if (res == AppErrorDialog.FORCE_QUIT) { long orig = Binder.clearCallingIdentity(); try { // Kill it with fire! //handleAppCrashLocked主要是结束activity,并更新oom_adj mService.mStackSupervisor.handleAppCrashLocked(r); if (!r.persistent) { //如果不是常驻应用,则在此处kill掉 mService.removeProcessLocked(r, false, false, "crash"); mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); } } finally { Binder.restoreCallingIdentity(orig); } } //选择强制停止并报告 if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) { //该函数中将生成错误信息,并构造一个Intent用于拉起报告界面 appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo); } if (r != null && !r.isolated && res != AppErrorDialog.RESTART) { // XXX Can't keep track of crash time for isolated processes, // since they don't have a persistent identity. //记录crash时间 mProcessCrashTimes.put(r.info.processName, r.uid, SystemClock.uptimeMillis()); } } if (appErrorIntent != null) { try { //如果选择了强制停止并报告,那么此时就会拉起报告界面 mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId)); } catch (ActivityNotFoundException e) { .............. } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
整体来看AMS处理crash的流程还是相当清晰的:
1、首先记录crash相关的信息到drop box;
2、如果存在可以处理App crash的ActivityController,那么将crash交给它处理;
否则,弹出crash对话框,然用户选择后续操作。
3、根据用户的选择,AMS可以进行重启应用、强行停止应用或拉起报告界面等操作。
不过上述流程中,在拉起对话框前,先调用了makeAppCrashingLocked函数。
若这个函数返回false,那么后续的流程就不会继续进行。
我们来看看这个函数的具体用途。
private boolean makeAppCrashingLocked(ProcessRecord app, String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) { app.crashing = true; //就是创建一个对象,其中包含了所有的错误信息 app.crashingReport = generateProcessError(app, ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); //前面的代码已经提到过,系统可以通过Intent拉起一个crash报告界面 //startAppProblemLocked函数,就是在系统中找到这个报告界面对应的ComponentName //此外,如果crash应用正好在处理有序广播,那么为了不影响后续广播接受器的处理, //startAppProblemLocked会停止crash应用对广播的处理流程, //即后续的广播接受器可以跳过crash应用,直接开始处理有序广播 startAppProblemLocked(app); //停止“冻结”屏幕 app.stopFreezingAllLocked(); //进行一些后续的处理 //从代码来看,如果应用不是在1min内连续crash,该函数都会返回true return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace, data);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
根据上面的代码,可以看出makeAppCrashingLocked函数最主要的工作主要有两个:
1、查找crash报告界面对应的componentName;
2、避免进程短时间内连续crash,导致频繁拉起对话框。
三、后续的清理工作
根据前面的流程,我们知道当进程crash后,最终将被kill掉,
此时AMS还需要完成后续的清理工作。
我们先来回忆一下进程启动后,注册到AMS的部分流程:
//进程启动后,对应的ActivityThread会attach到AMS上private final boolean attachApplicationLocked(IApplicationThread thread, int pid) { ............ ProcessRecord app; if (pid != MY_PID && pid >= 0) { synchronized (mPidsSelfLocked) { app = mPidsSelfLocked.get(pid); } } else { app = null; } ............ final String processName = app.processName; try { //生成了一个“讣告”接收者 AppDeathRecipient adr = new AppDeathRecipient( app, pid, thread); thread.asBinder().linkToDeath(adr, 0); app.deathRecipient = adr; } catch (RemoteException e) { app.resetPackageList(mProcessStats); startProcessLocked(app, "link fail", processName); return false; } ................}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
从上面的代码可以看出,当进程注册到AMS时,AMS注册了一个“讣告”接收者注册到进程中。
因此,当crash进程被kill后,AppDeathRecipient中的binderDied函数将被回调:
@Overridepublic void binderDied() { .......... synchronized(ActivityManagerService.this) { appDiedLocked(mApp, mPid, mAppThread, true); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
根据代码可知,接收到进程“死亡”的通知后,最后还是调用AMS的appDiedLocked函数进行处理:
final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread, boolean fromBinderDied) { // First check if this ProcessRecord is actually active for the pid. synchronized (mPidsSelfLocked) { ProcessRecord curProc = mPidsSelfLocked.get(pid); if (curProc != app) { ........... return; } } ............. if (!app.killed) { if (!fromBinderDied) { Process.killProcessQuiet(pid); } killProcessGroup(app.uid, pid); app.killed = true; } //以上都是一些保证健壮性的代码 if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) { //进程是正常启动的,非测试启动,那么需要内存调整 boolean doLowMem = app.instrumentationClass == null; boolean doOomAdj = doLowMem; if (!app.killedByAm) { ............ mAllowLowerMemLevel = true; } else { mAllowLowerMemLevel = false; doLowMem = false; } .............. //handleAppDiedLocked进行实际的工作 handleAppDiedLocked(app, false, true); if (doOomAdj) { //重新更新进程的oom_adj updateOomAdjLocked(); } if (doLowMem) { //在必要时,触发系统中的进程做内存回收 doLowMemReportIfNeededLocked(app); } }......... ..........}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
appDiedLocked函数中比较重要的是handleAppDiedLocked函数:
private final void handleAppDiedLocked(ProcessRecord app, boolean restarting, boolean allowRestart) { int pid = app.pid; //进行进程中service、ContentProvider、BroadcastReceiver等的收尾工作 //这个函数虽然很长,但实际的功能还是很清晰的,这里不作进一步展开 //比较重要的是:1、对于crash进程中的Bounded Service而言,会清理掉service与客户端之间的联系; //此外若service的客户端重要性过低,还会被直接kill掉 //2、清理ContentProvider时,在removeDyingProviderLocked函数中,可能清理掉其客户端进程(对于stable contentProvider而言) boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1); if (!kept && !restarting) { //不再保留和重启时,从LRU表中移除 removeLruProcessLocked(app); if (pid > 0) { ProcessList.remove(pid); } } .................. // Remove this application's activities from active lists. //进行Activity相关的收尾工作 boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app); app.activities.clear(); if (app.instrumentationClass != null) { .............. } if (!restarting && hasVisibleActivities && !mStackSupervisor.resumeFocusedStackTopActivityLocked()) { // If there was nothing to resume, and we are not already restarting this process, but // there is a visible activity that is hosted by the process... then make sure all // visible activities are running, taking care of restarting this process. // 从注释来看,若当前只有crash进程中存在可视Activity,那么AMS还是会试图重启该进程 mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
上述代码中cleanUpApplicationRecordLocked函数,在此不做深入分析。
其中唯一比较麻烦的就是Bounded Service和ContentProvider的清理,
因为这两种组件全部要考虑其客户端进程。
四、总结
整体来讲,Android中进程crash后的处理流程基本上如上图所示。
这个流程相对来说是比较简单的,唯一麻烦点的地方可能是进程结束后,
AMS进行的清理工作。
- Android 7.0 ActivityManagerService(10) App的crash处理
- Android 7.0 ActivityManagerService(10) App的crash处理
- ActivityManagerService native crash处理流程
- android app 默认的crash处理流程 (FATAL EXCEPTION)
- Android App线上crash处理方法论
- Android app 与 ActivityManagerService的通信原理(简化版)
- 处理、分析iOS App的Crash Reports
- Android App Crash类型
- Android App Crash类型
- [Android随笔]Android关于Crash的处理
- 处理APP Review Team给你发的.crash文件
- Android 7.0 ActivityManagerService(1) AMS的启动过程
- Android 7.0 ActivityManagerService(2) 启动Activity的过程:一
- Android 7.0 ActivityManagerService(3) 启动Activity的过程:二
- Android 7.0 ActivityManagerService(4) 启动Activity的过程:三
- Android 7.0 ActivityManagerService(1) AMS的启动过程
- Android 7.0 ActivityManagerService(2) 启动Activity的过程:一
- Android 7.0 ActivityManagerService(3) 启动Activity的过程:二
- Python使用pandas处理CSV文件
- 基于keepalived+nginx部署强健的高可用7层负载均衡方案
- 阿里巴巴2017实习生招聘研发工程师JAVA考试 最懵逼的逻辑题
- 深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接
- AngularJS入门 AngularJS控制器 AngularJS常见内置指令
- Android 7.0 ActivityManagerService(10) App的crash处理
- ios 定时 button
- AMD Linux驱动团提供对Vega的全面支持
- 阿斯达打奥德赛
- Android-25种开源炫酷动画框架
- 201509-3 模板生成系统
- _.debounce 应用
- new创建对象的如何传递构造参数-c++
- 数据结构与算法之DFS/BFS解决扫雷问题<十五>