Android后臺殺死系列之二:ActivityManagerService與App現場恢復機制
本篇是Android后臺殺死系列的第二篇,主要講解ActivityMangerService是如何恢復被后臺殺死的進程的(基于4.3 ),在開篇FragmentActivity及PhoneWindow后臺殺死處理機制 中,簡述了后臺殺死所引起的一些常見問題,還有Android系統控件對后臺殺死所做的一些兼容,以及onSaveInstance跟onRestoreInstance的作用于執行時機,最后說了如何應對后臺殺死,但是對于被后臺殺死的進程如何恢復的并沒有講解,本篇不涉及后臺殺死,比如LowmemoryKiller機制,只講述被殺死的進程如何恢復的。假設,一個應用被后臺殺死,再次從最近的任務列表喚起App時候,系統是如何處理的呢?有這么幾個問題可能需要解決:
- Android框架層(AMS)如何知道App被殺死了
- App被殺前的場景是如何保存的
- 系統(AMS)如何恢復被殺的App
- 被后臺殺死的App的啟動流程跟普通的啟動有什么區別
- Activity的恢復順序為什么是倒序恢復
系統(AMS)如何知道App被殺死了
首先來看第一個問題,系統如何知道Application被殺死了,Android使用了Linux的oomKiller機制,只是簡單的做了個變種,采用分等級的LowmemoryKiller,但這個其實是內核層面的,LowmemoryKiller殺死進程后,不會像用戶空間發送通知,也就是說框架層的ActivityMangerService無法知道App是否被殺死,但是,只有知道App或者Activity是否被殺死,AMS(ActivityMangerService)才能正確的走喚起流程,那么AMS究竟是在什么時候知道App或者Activity被后臺殺死了呢?我們先看一下從最近的任務列表進行喚起的時候,究竟發生了什么。
從最近的任務列表或者Icon再次喚起App的流程
在系統源碼systemUi的包里,有個RecentActivity,這個其實就是最近的任務列表的入口,而其呈現界面是通過RecentsPanelView來展現的,點擊最近的App其執行代碼如下:
- public void handleOnClick(View view) {
- ViewHolder holder = (ViewHolder)view.getTag();
- TaskDescription ad = holder.taskDescription;
- final Context context = view.getContext();
- final ActivityManager am = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
- Bitmap bm = holder.thumbnailViewImageBitmap;
- ...
- // 關鍵點 1 如果TaskDescription沒有被主動關閉,正常關閉,ad.taskId就是>=0
- if (ad.taskId >= 0) {
- // This is an active task; it should just go to the foreground.
- am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
- opts);
- } else {
- Intent intent = ad.intent;
- intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
- | Intent.FLAG_ACTIVITY_TASK_ON_HOME
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- context.startActivityAsUser(intent, opts,
- new UserHandle(UserHandle.USER_CURRENT));
- }...
- }
在上面的代碼里面,有個判斷ad.taskId >= 0,如果滿足這個條件,就通過moveTaskToFront喚起APP,那么ad.taskId是如何獲取的?recent包里面有各類RecentTasksLoader,這個類就是用來加載最近任務列表的一個Loader,看一下它的源碼,主要看一下加載:
- @Override
- protected Void doInBackground(Void... params) {
- // We load in two stages: first, we update progress with just the first screenful
- // of items. Then, we update with the rest of the items
- final int origPri = Process.getThreadPriority(Process.myTid());
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- final PackageManager pm = mContext.getPackageManager();
- final ActivityManager am = (ActivityManager)
- mContext.getSystemService(Context.ACTIVITY_SERVICE);
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
- ....
- TaskDescription item = createTaskDescription(recentInfo.id,
- recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description);
- ....
- }
可以看到,其實就是通過ActivityManger的getRecentTasks向AMS請求最近的任務信息,然后通過createTaskDescription創建TaskDescription,這里傳遞的recentInfo.id其實就是TaskDescription的taskId,來看一下它的意義:
- public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
- int flags, int userId) {
- ...
- IPackageManager pm = AppGlobals.getPackageManager();
- final int N = mRecentTasks.size();
- ...
- for (int i=0; i<N && maxNum > 0; i++) {
- TaskRecord tr = mRecentTasks.get(i);
- if (i == 0
- || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0)
- || (tr.intent == null)
- || ((tr.intent.getFlags()
- &Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)) {
- ActivityManager.RecentTaskInfo rti
- = new ActivityManager.RecentTaskInfo();
- rti.id = tr.numActivities > 0 ? tr.taskId : -1;
- rti.persistentId = tr.taskId;
- rti.baseIntent = new Intent(
- tr.intent != null ? tr.intent : tr.affinityIntent);
- if (!detailed) {
- rti.baseIntent.replaceExtras((Bundle)null);
- }
可以看出RecentTaskInfo的id是由TaskRecord決定的,如果TaskRecord中numActivities > 0就去TaskRecord的Id,否則就取-1,這里的numActivities其實就是TaskRecode中記錄的ActivityRecord的數目,更具體的細節可以自行查看ActivityManagerService及ActivityStack,那么這里就容易解釋了,只要是存活的APP、或者被LowmemoryKiller殺死的APP,其AMS的ActivityRecord是完整保存的,這就是恢復的依據。RecentActivity獲取的數據其實就是AMS中的翻版,RecentActivity并不知道將要喚起的APP是否是存活的,只要TaskRecord告訴RecentActivity是存貨的,那么久直接走喚起流程,也就是通過ActivityManager的moveTaskToFront喚起App,至于后續的工作,就完全交給AMS來處理。現看一下到這里的流程圖:
在喚起App的時候AMS偵測App或者Activity是否被異常殺死
接著往下看moveTaskToFrontLocked,這個函數在ActivityStack中,ActivityStack主要用來管理ActivityRecord棧的,所有start的Activity都在ActivityStack中保留一個ActivityRecord,這個也是AMS管理Activity的一個依據,ActivityStack最終moveTaskToFrontLocked會調用resumeTopActivityLocked來喚起Activity,AMS獲取即將resume的Activity信息的方式主要是通過ActivityRecord,它并不知道Activity本身是否存活,獲取之后,AMS知道喚醒Activity的環節才知道App或者Activity被殺死,具體看一下resumeTopActivityLocked源碼:
- final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
- // This activity is now becoming visible.
- mService.mWindowManager.setAppVisibility(next.appToken, true);
- .... 恢復邏輯
- if (next.app != null && next.app.thread != null) {
- // 正常恢復
- try {
- // Deliver all pending results.
- ArrayList a = next.results;
- if (a != null) {
- final int N = a.size();
- if (!next.finishing && N > 0) {
- next.app.thread.scheduleSendResult(next.appToken, a);
- }
- }
- ...
- next.app.thread.scheduleResumeActivity(next.appToken,
- mService.isNextTransitionForward());
- ...
- } catch (Exception e) {
- // Whoops, need to restart this activity!
- // 這里需要重啟,難道被后臺殺死,走的是異常分支嗎???? 異常殺死
- if (DEBUG_STATES) Slog.v(TAG, "Resume failed; resetting state to "
- + lastState + ": " + next);
- next.state = lastState;
- mResumedActivity = lastResumedActivity;
- <!--確實這里是因為進程掛掉了-->
- Slog.i(TAG, "Restarting because process died: " + next);
- 。。。
- startSpecificActivityLocked(next, true, false);
- return true;
- }
- ...
- }
由于沒有主動調用finish的,所以AMS并不會清理掉ActivityRecord與TaskRecord ,因此resume的時候走的就是上面的分支,可以這里會調用next.app.thread.scheduleSendResult或者next.app.thread.scheduleResumeActivity進行喚起上一個Activity,但是如果APP或者Activity被異常殺死,那么喚起的操作一定是失敗,會拋出異常,首先假設APP整個被殺死,那么APP端同AMS通信的Binder線程也不復存在,這個時候通過Binder進行通信就會拋出RemoteException,如此,就會走下面的catch部分,通過startSpecificActivityLocked再次將APP重建,并且將最后的Activity重建,其實你可以本地利用AIDL寫一個C/S通信,在將一端關閉,然后用另一端訪問,就會拋出RemoteException異常,如下圖:
還有一種可能,APP沒有被kill,但是Activity被Kill掉了,這個時候會怎么樣?首先,Activity的管理是一定通過AMS的,Activity的kill一定是是AMS操刀的,是有記錄的,嚴格來說,這種情況并不屬于后臺殺死,因為這屬于AMS正常的管理,在可控范圍,比如打開了開發者模式中的“不保留活動”,這個時候,雖然會殺死Activity,但是仍然保留了ActivitRecord,所以再喚醒,或者回退的的時候仍然有跡可循,看一下ActivityStack的Destroy回調代碼,
- final boolean destroyActivityLocked(ActivityRecord r,
- boolean removeFromApp, boolean oomAdj, String reason) {
- ...
- if (hadApp) {
- ...
- boolean skipDestroy = false;
- try {
- 關鍵代碼 1
- r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
- r.configChangeFlags);
- ...
- if (r.finishing && !skipDestroy) {
- if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYING: " + r
- + " (destroy requested)");
- r.state = ActivityState.DESTROYING;
- Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG);
- msg.obj = r;
- mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
- } else {
- 關鍵代碼 2
- r.state = ActivityState.DESTROYED;
- if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r);
- r.app = null;
- }
- }
- return removedFromHistory;
- }
這里有兩個關鍵啊你單,1是告訴客戶端的AcvitityThread清除Activity,2是標記如果AMS自己非正常關閉的Activity,就將ActivityRecord的state設置為ActivityState.DESTROYED,并且清空它的ProcessRecord引用:r.app = null。這里是喚醒時候的一個重要標志,通過這里AMS就能知道Activity被自己異常關閉了,設置ActivityState.DESTROYED是為了讓避免后面的清空邏輯。
- final void activityDestroyed(IBinder token) {
- synchronized (mService) {
- final long origId = Binder.clearCallingIdentity();
- try {
- ActivityRecord r = ActivityRecord.forToken(token);
- if (r != null) {
- mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
- }
- int index = indexOfActivityLocked(r);
- if (index >= 0) {
- 1 <!--這里會是否從history列表移除ActivityRecord-->
- if (r.state == ActivityState.DESTROYING) {
- cleanUpActivityLocked(r, true, false);
- removeActivityFromHistoryLocked(r);
- }
- }
- resumeTopActivityLocked(null);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
看代碼關鍵點1,只有r.state == ActivityState.DESTROYING的時候,才會移除ActivityRecord,但是對于不非正常finish的Activity,其狀態是不會被設置成ActivityState.DESTROYING,是直接跳過了ActivityState.DESTROYING,被設置成了ActivityState.DESTROYED,所以不會removeActivityFromHistoryLocked,也就是保留了ActivityRecord現場,好像也是依靠異常來區分是否是正常的結束掉Activity。這種情況下是如何啟動Activity的呢? 通過上面兩點分析,就知道了兩個關鍵點
- ActivityRecord沒有動HistoryRecord列表中移除
- ActivityRecord 的ProcessRecord字段被置空,r.app = null
這樣就保證了在resumeTopActivityLocked的時候,走startSpecificActivityLocked分支
- final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
- ...
- if (next.app != null && next.app.thread != null) {
- ...
- } else {
- // Whoops, need to restart this activity!
- ...
- startSpecificActivityLocked(next, true, true);
- }
- return true;
- }
到這里,AMS就知道了這個APP或者Activity是不是被異常殺死過,從而,決定是走resume流程還是restore流程。
App被殺前的場景是如何保存的: 新Activity啟動跟舊Activity的保存
App現場的保存流程相對是比較簡單的,入口基本就是startActivity的時候,只要是界面的跳轉基本都牽扯到Activity的切換跟當前Activity場景的保存:先畫個簡單的圖形,開偏里面講FragmentActivity的時候,簡單說了一些onSaveInstance的執行時機,這里詳細看一下AMS是如何管理這些跳轉以及場景保存的,模擬場景:Activity A 啟動Activity B的時候,這個時候A不可見,可能會被銷毀,需要保存A的現場,這個流程是什么樣的:簡述如下
- ActivityA startActivity ActivityB
- ActivityA pause
- ActivityB create
- ActivityB start
- ActivityB resume
- ActivityA onSaveInstance
- ActivityA stop
流程大概是如下樣子:
現在我們通過源碼一步一步跟一下,看看AMS在新Activity啟動跟舊Activity的保存的時候,到底做了什么:跳過簡單的startActivity,直接去AMS中去看
ActivityManagerService
- public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
- Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, int startFlags,
- String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
- enforceNotIsolatedCaller("startActivity");
- ...
- return mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
- null, null, options, userId);
- }
ActivityStack
- final int startActivityMayWait(IApplicationThread caller, int callingUid,
- int res = startActivityLocked(caller, intent, resolvedType,
- aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
- callingPackage, startFlags, options, componentSpecified, null);
- 。。。
- }
這里通過startActivityMayWait啟動新的APP,或者新Activity,這里只看簡單的,至于從桌面啟動App的流程,可以去參考更詳細的文章,比如老羅的startActivity流程,大概就是新建ActivityRecord,ProcessRecord之類,并加入AMS中相應的堆棧等,resumeTopActivityLocked是界面切換的統一入口,第一次進來的時候,由于ActivityA還在沒有pause,因此需要先暫停ActivityA,這些完成后,
ActivityStack
- final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
- ...
- <!--必須將當前Resume的Activity設置為pause 然后stop才能繼續-->
- // We need to start pausing the current activity so the top one
- // can be resumed...
- if (mResumedActivity != null) {
- if (next.app != null && next.app.thread != null) {
- mService.updateLruProcessLocked(next.app, false);
- }
- startPausingLocked(userLeaving, false);
- return true;
- }
- ....
其實這里就是暫停ActivityA,AMS通過Binder告訴ActivityThread需要暫停的ActivityA,ActivityThread完成后再通過Binder通知AMS,AMS會開始resume ActivityB,
- private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {
- if (prev.app != null && prev.app.thread != null) {
- ...
- try {
- prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
- userLeaving, prev.configChangeFlags);
ActivityThread
- private void handlePauseActivity(IBinder token, boolean finished,
- boolean userLeaving, int configChanges) {
- ActivityClientRecord r = mActivities.get(token);
- if (r != null) {
- ...
- performPauseActivity(token, finished, r.isPreHoneycomb());
- ...
- // Tell the activity manager we have paused.
- try {
- ActivityManagerNative.getDefault().activityPaused(token);
- } catch (RemoteException ex) {
- }
- }
- }
AMS收到ActivityA發送過來的pause消息之后,就會喚起ActivityB,入口還是resumeTopActivityLocked,喚醒B,之后還會A給進一步stop掉,這個時候就牽扯到現場的保存,
ActivityStack
- private final void completePauseLocked() {
- if (!mService.isSleeping()) {
- resumeTopActivityLocked(prev);
- } else {
- ...
ActivityB如何啟動的,本文不關心,只看ActivityA如何保存現場的,ActivityB起來后,會通過ActivityStack的stopActivityLocked去stop ActivityA,
- private final void stopActivityLocked(ActivityRecord r) {
- ...
- if (mMainStack) {
- r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags);
- ...
- }
回看APP端,看一下ActivityThread中的調用:首先通過callActivityOnSaveInstanceState,將現場保存到Bundle中去,
- private void performStopActivityInner(ActivityClientRecord r,
- StopInfo info, boolean keepShown, boolean saveState) {
- ...
- // Next have the activity save its current state and managed dialogs...
- if (!r.activity.mFinished && saveState) {
- if (r.state == null) {
- state = new Bundle();
- state.setAllowFds(false);
- mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
- r.state = state;
- 。。。
- }
之后,通過ActivityManagerNative.getDefault().activityStopped,通知AMS Stop動作完成,在通知的時候,還會將保存的現場數據帶過去。
- private static class StopInfo implements Runnable {
- ActivityClientRecord activity;
- Bundle state;
- Bitmap thumbnail;
- CharSequence description;
- @Override public void run() {
- // Tell activity manager we have been stopped.
- try {
- ActivityManagerNative.getDefault().activityStopped(
- activity.token, state, thumbnail, description);
- } catch (RemoteException ex) {
- }
- }
- }
通過上面流程,AMS不僅啟動了新的Activity,同時也將上一個Activity的現場進行了保存,及時由于種種原因上一個Actiivity被殺死,在回退,或者重新喚醒的過程中AMS也能知道如何喚起Activiyt,并恢復。
現在解決兩個問題,1、如何保存現場,2、AMS怎么判斷知道APP或者Activity是否被異常殺死,那么就剩下最后一個問題了,AMS如何恢復被異常殺死的APP或者Activity呢。
整個Application被后臺殺死情況下的恢復邏輯
其實在講解AMS怎么判斷知道APP或者Activity是否被異常殺死的時候,就已經涉及了恢復的邏輯,也知道了一旦AMS知道了APP被后臺殺死了,那就不是正常的resuem流程了,而是要重新laucher,先來看一下整個APP被干掉的會怎么處理,看resumeTopActivityLocked部分,從上面的分析已知,這種場景下,會因為Binder通信拋異常走異常分支,如下:
- final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
- ....
- if (next.app != null && next.app.thread != null) {
- if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next);
- ...
- try {
- ...
- } catch (Exception e) {
- // Whoops, need to restart this activity!
- 這里是知道整個app被殺死的
- Slog.i(TAG, "Restarting because process died: " + next);
- next.state = lastState;
- mResumedActivity = lastResumedActivity;
- Slog.i(TAG, "Restarting because process died: " + next);
- startSpecificActivityLocked(next, true, false);
- return true;
- }
從上面的代碼可以知道,其實就是走startSpecificActivityLocked,這根第一次從桌面喚起APP沒多大區別,只是有一點需要注意,那就是這種時候啟動的Activity是有上一次的現場數據傳遞過得去的,因為上次在退到后臺的時候,所有Activity界面的現場都是被保存了,并且傳遞到AMS中去的,那么這次的恢復啟動就會將這些數據返回給ActivityThread,再來仔細看一下performLaunchActivity里面關于恢復的特殊處理代碼:
- private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
- ActivityInfo aInfo = r.activityInfo;
- Activity activity = null;
- try {
- java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
- activity = mInstrumentation.newActivity(
- cl, component.getClassName(), r.intent);
- StrictMode.incrementExpectedActivityCount(activity.getClass());
- r.intent.setExtrasClassLoader(cl);
- if (r.state != null) {
- r.state.setClassLoader(cl);
- }
- } catch (Exception e) {
- ...
- }
- try {
- Application app = r.packageInfo.makeApplication(false, mInstrumentation);
- ...
- 關鍵點 1
- mInstrumentation.callActivityOnCreate(activity, r.state);
- ...
- r.activity = activity;
- r.stopped = true;
- if (!r.activity.mFinished) {
- activity.performStart();
- r.stopped = false;
- }
- 關鍵點 1
- if (!r.activity.mFinished) {
- if (r.state != null) {
- mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
- }
- }
- if (!r.activity.mFinished) {
- activity.mCalled = false;
- mInstrumentation.callActivityOnPostCreate(activity, r.state);
- ...
- }
看一下關鍵點1跟2,先看關鍵點1,mInstrumentation.callActivityOnCreate會回調Actiivyt的onCreate,這個函數里面其實主要針對FragmentActivity做一些Fragment恢復的工作,ActivityClientRecord中的r.state是AMS傳給APP用來恢復現場的,正常啟動的時候,這些都是null。再來看關鍵點2 ,在r.state != null非空的時候執行mInstrumentation.callActivityOnRestoreInstanceState,這個函數默認主要就是針對Window做一些恢復工作,比如ViewPager恢復之前的顯示位置等,也可以用來恢復用戶保存數據。
Application沒有被后臺殺死,Activity被殺死的恢復
打開開發者模式”不保留活動“,就是這種場景,在上面的分析中,知道,AMS主動異常殺死Activity的時候,將AcitivityRecord的app字段置空,因此resumeTopActivityLocked同整個APP被殺死不同,會走下面的分支
- final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
- ...
- if (next.app != null && next.app.thread != null) {
- ...
- } else {
- 關鍵點 1 只是重啟Activity,可見這里其實是知道的,進程并沒死,
- // Whoops, need to restart this activity!
- startSpecificActivityLocked(next, true, true);
- }
- return true;
- }
雖然不太一樣,但是同樣走startSpecificActivityLocked流程,只是不新建APP進程,其余的都是一樣的,不再講解。到這里,我們應該就了解了,
- Android是如何在預防的情況下保存場景
- AMS如何知道APP是否被后臺殺死
- AMS如何根據ActivityStack重建APP被殺死時的場景
到這里ActivityManagerService恢復APP場景的邏輯就應該講完了。再碎碎念一些問題,可能是一些面試的點。
- 主動清除最近任務跟異常殺死的區別:ActivityStack是否正常清楚
- 恢復的時候,為什么是倒序恢復:因為這是ActivityStack中的HistoryRecord中棧的順序,嚴格按照AMS端來
- 一句話概括Android后臺殺死恢復原理:Application進程被Kill,但現場被AMS保存,AMS能根據保存恢復Application現場