Android後臺殺死系列之二:ActivityManagerService與App現場恢復機制

AMS與後臺殺死

本篇是Android後臺殺死系列的第二篇,主要講解ActivityMangerService是如何恢復被後臺殺死的進程的(基於4.3 ),在開篇 FragmentActivity及PhoneWindow後臺殺死處理機制 中,簡述了後臺殺死所引發的一些常見問題,還有Android系統控件對後臺殺死所作的一些兼容,以及onSaveInstance跟onRestoreInstance的做用於執行時機,最後說了如何應對後臺殺死,可是對於被後臺殺死的進程如何恢復的並無講解,本篇不涉及後臺殺死,好比LowmemoryKiller機制,只講述被殺死的進程如何恢復的。假設,一個應用被後臺殺死,再次從最近的任務列表喚起App時候,系統是如何處理的呢?有這麼幾個問題可能須要解決:html

  • Android框架層(AMS)如何知道App被殺死了java

  • App被殺前的場景是如何保存的android

  • 系統(AMS)如何恢復被殺的Appgit

  • 被後臺殺死的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,看一下它的源碼,主要看一下加載:app

@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的流程

整個APP被後臺殺死的狀況下AMS是如何恢復現場的

AMS與客戶端的通訊是經過Binder來進行的,而且通訊是」全雙工「的,且互爲客戶端跟服務器,也就說AMS向客戶端發命令的時候,AMS是客戶端,反之亦然。注意 Binder有個訃告的功能的:若是基於Binder通訊的服務端(S)若是掛掉了,客戶端(C)可以收到Binder驅動發送的一份訃告,告知客戶端Binder服務掛了,能夠把Binder驅動看做是第三方不死郵政機構,專門向客戶端發偶像死亡通知。對於APP被異常殺死的狀況下,這份訃告是發送給AMS的,AMS在收到通知後,就會針對APP被異常殺死的狀況做出整理,這裏牽扯到Binder驅動的代碼有興趣能夠本身翻一下。之類直接衝訃告接受後端處理邏輯來分析,在AMS源碼中,入口其實就是appDiedLocked.

final void appDiedLocked(ProcessRecord app, int pid,
            IApplicationThread thread) {
              ...
            if (app.pid == pid && app.thread != null &&
                app.thread.asBinder() == thread.asBinder()) {
            boolean doLowMem = app.instrumentationClass == null;
            關鍵點1 
            handleAppDiedLocked(app, false, true);
             // 若是是被後臺殺了,怎麼處理
             關鍵點2 
            if (doLowMem) {
                boolean haveBg = false;
                for (int i=mLruProcesses.size()-1; i>=0; i--) {
                    ProcessRecord rec = mLruProcesses.get(i);
                    if (rec.thread != null && rec.setAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) {
                        haveBg = true;
                        break;
                    }
                }
                if (!haveBg) {
                <!--若是被LowmemoryKiller殺了,就說明內存緊張,這個時候就會通知其餘後臺APP,當心了,趕忙釋放資源-->
                    EventLog.writeEvent(EventLogTags.AM_LOW_MEMORY, mLruProcesses.size());
                    long now = SystemClock.uptimeMillis();
                    for (int i=mLruProcesses.size()-1; i>=0; i--) {
                        ProcessRecord rec = mLruProcesses.get(i);
                        if (rec != app && rec.thread != null &&
                                (rec.lastLowMemory+GC_MIN_INTERVAL) <= now) {
                            if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
                                rec.lastRequestedGc = 0;
                            } else {
                                rec.lastRequestedGc = rec.lastLowMemory;
                            }
                            rec.reportLowMemory = true;
                            rec.lastLowMemory = now;
                            mProcessesToGc.remove(rec);
                            addProcessToGcListLocked(rec);
                        }
                    }
                    mHandler.sendEmptyMessage(REPORT_MEM_USAGE);
                    <!--縮減資源-->
                    scheduleAppGcsLocked();
                }
            }
        }
        ...
    }

先看關鍵點1:在進程被殺死後,AMS端要選擇性清理進程相關信息,清理後,再根據是否是內存低引發的後臺殺死,決定是否是須要清理其餘後臺進程。接着看handleAppDiedLocked如何清理的,這裏有重建時的依據:ActivityRecord不清理,可是爲它設置個APP未綁定的標識

private final void handleAppDiedLocked(ProcessRecord app,
        boolean restarting, boolean allowRestart) {
    
    關鍵點1
    cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
    ...
    關鍵點2
     // Remove this application's activities from active lists.
    boolean hasVisibleActivities = mMainStack.removeHistoryRecordsForAppLocked(app);

    app.activities.clear();
    ...
     關鍵點3
    if (!restarting) {
        if (!mMainStack.resumeTopActivityLocked(null)) {
            // 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.
            if (hasVisibleActivities) {
                mMainStack.ensureActivitiesVisibleLocked(null, 0);
            }
        }
    }
}

看關鍵點1,cleanUpApplicationRecordLocked,主要負責清理一些Providers,receivers,service之類的信息,而且在清理過程當中根據配置的一些信息決定是否須要重建進程並啓動,關鍵點2 就是關係到喚起流程的判斷,關鍵點3,主要是被殺的進程是不是當前前臺進程,若是是,須要重建,並當即顯示:先簡單看cleanUpApplicationRecordLocked的清理流程

private final void cleanUpApplicationRecordLocked(ProcessRecord app,
        boolean restarting, boolean allowRestart, int index) {
        
   <!--清理service-->
    mServices.killServicesLocked(app, allowRestart);
    ...
    boolean restart = false;
   <!--清理Providers.-->
    if (!app.pubProviders.isEmpty()) {
        Iterator<ContentProviderRecord> it = app.pubProviders.values().iterator();
        while (it.hasNext()) {
            ContentProviderRecord cpr = it.next();
            。。。
        app.pubProviders.clear();
    } ...
     <!--清理receivers.-->
     // Unregister any receivers.
    if (app.receivers.size() > 0) {
        Iterator<ReceiverList> it = app.receivers.iterator();
        while (it.hasNext()) {
            removeReceiverLocked(it.next());
        }
        app.receivers.clear();
    }
    ... 關鍵點1,進程是夠須要重啓,
    if (restart && !app.isolated) {
        // We have components that still need to be running in the
        // process, so re-launch it.
        mProcessNames.put(app.processName, app.uid, app);
        startProcessLocked(app, "restart", app.processName);
    } 
     ...
}

從關鍵點1就能知道,這裏是隱藏了進程是否須要重啓的邏輯,好比一個Service設置了START_STICKY,被殺後,就須要從新喚起,這裏也是流氓軟件肆虐的緣由。再接着看mMainStack.removeHistoryRecordsForAppLocked(app),對於直觀理解APP重建
,這句代碼處於核心的地位,

boolean removeHistoryRecordsForAppLocked(ProcessRecord app) {
    ...
    while (i > 0) {
        i--;
        ActivityRecord r = (ActivityRecord)mHistory.get(i);
        if (r.app == app) {
            boolean remove;
            <!--關鍵點1-->
            if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
                remove = true;
            } else if (r.launchCount > 2 &&
                remove = true;
            } else {
             //通常來說,走false
                remove = false;
            }
            <!--關鍵點2-->
            if (remove) {
                ...
                removeActivityFromHistoryLocked(r);

            } else {
                ...
                r.app = null;
                ...
    }

    return hasVisibleActivities;
}

在Activity跳轉的時候,準確的說,在stopActivity以前,會保存Activity的現場,這樣在AMS端r.haveState==true,也就是說,其ActivityRecord不會被從ActivityStack中移除,同時ActivityRecord的app字段被置空,這裏在恢復的時候,是決定resume仍是重建的關鍵。接着往下看moveTaskToFrontLocked,這個函數在ActivityStack中,主要管理ActivityRecord棧的,全部start的Activity都在ActivityStack中保留一個ActivityRecord,這個也是AMS管理Activiyt的一個依據,最終moveTaskToFrontLocked會調用resumeTopActivityLocked來喚起Activity,AMS獲取即將resume的Activity信息的方式主要是經過ActivityRecord,它並不知道Activity自己是否存活,獲取以後,AMS在喚醒Activity的環節才知道App或者Activity被殺死,具體看一下resumeTopActivityLocked源碼:

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
          ...
         關鍵點1 
        if (next.app != null && next.app.thread != null) { 
        ...
        
        } else {
            // Whoops, need to restart this activity!
          ...
            startSpecificActivityLocked(next, true, true);
        }

        return true;
    }

看關鍵點1的判斷條件,因爲已經將ActivityRecord的app字段置空,AMS就知道了這個APP或者Activity被異常殺死過,所以,就會走startSpecificActivityLocked進行重建。 其實仔細想一想很簡單,對於主動調用finish的,AMS並不會清理掉ActivitRecord,在喚起APP的時候,若是AMS檢測到APP還存活,就走scheduleResumeActivity進行喚起上一個Activity,可是若是APP或者Activity被異常殺死過,那麼AMS就經過startSpecificActivityLocked再次將APP重建,而且將最後的Activity重建。

APP存活,可是Activity被後臺殺死的狀況下AMS是如何恢復現場的

還有一種可能,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的呢? 經過上面兩點分析,就知道了兩個關鍵點

  1. ActivityRecord沒有動HistoryRecord列表中移除

  2. 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

流程大概是以下樣子:

新Activity加載以及前Activity保存流程

如今咱們經過源碼一步一步跟一下,看看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,並恢復。

如今解決兩個問題,一、如何保存現場,二、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現場

僅供參考,歡迎指正

參考文檔

Android應用程序啓動過程源代碼分析
Android Framework架構淺析之【近期任務】
Android Low Memory Killer介紹
Android開發之InstanceState詳解
對Android近期任務列表(Recent Applications)的簡單分析
Android——內存管理-lowmemorykiller 機制
Android 操做系統的內存回收機制
Android LowMemoryKiller原理分析
Android進程生命週期與ADJ
Linux下/proc目錄簡介
startActivity啓動過程分析 精
Activity銷燬流程

相關文章
相關標籤/搜索