深入理解Activity啟動流程和AMS框架(三)

最好不过如今發表於2024-07-29

連結https://cloud.tencent.com/developer/article/1601480

續:

深入理解Activity啟動流程和AMS框架(一)

深入理解Activity啟動流程和AMS框架(二)

5、Task和LauncherMode

(1)、如何才能開始一個新的Task?

Intent中定義了一個標誌FLAGACTIVITYNEW_TASK,在startActivity的Intent引數中加入該標誌就能開啟一個新的Task。但是,如果系統中已經有相同affinity的Task存在,這時候就不會再啟動一個Task,而是將舊的Task帶到前臺。 Affinity的意思是“親和度”、“密切關係”,它的型別是字串,我們可以把它理解成Task的名稱。Affinity字串在系統中是唯一的,AMS查詢一個Task,最優先比較它的affinity。ActivityStack類中用來查詢Task的方法是findTaskLocked()。

程式碼語言:javascript
複製
ActivityRecord findTaskLocked(ActivityRecord target) {
     ......
     for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
         final TaskRecord task = mTaskHistory.get(taskNdx);
         ......
         if (!isDocument && !taskIsDocument && task.rootAffinity != null) {
             if (task.rootAffinity.equals(target.taskAffinity)) {
                 return r;
             }
         } else if (taskIntent != null && taskIntent.getComponent() != null &&
                 taskIntent.getComponent().compareTo(cls) == 0 &&
                 Objects.equals(documentData, taskDocumentData)) {
             return r;
         } else if (affinityIntent != null && affinityIntent.getComponent() != null &&
                 affinityIntent.getComponent().compareTo(cls) == 0 &&
                 Objects.equals(documentData, taskDocumentData)) {
             return r;
         }
     }     return null;
}

findTaskLocked()方法首先遍歷已有TaskRecord物件的affinity變數是否等於ActivityRecord的taskAffinity變數,如果相同就直接把舊的Task帶回前臺,而不是new一個新的TaskRecord。 既然一個Task的affinity這麼重要,它是在哪裡定義的呢?在AndroidManifest.xml檔案中: - Activity標籤的taskAffinity屬性:當使用標誌FLAGACTIVITYNEW_TASK啟動一個Activity時才起作用 - Application標籤的taskAffinity屬性:沒有指定activity標籤的taskAffinity屬性的,將會繼承application標籤的taskAffinity屬性 - 應用的包名packageName:沒有指定的情況,預設值

通常在開發中,很少應用會自定義一個taskAffinity屬性,所以預設就是其包名。因此,在應用中如果啟動本應用的另一個Activity,即便intent裡新增了FLAGACTIVITYNEW_TASK也不一定會啟動一個新的Task,除非這個Activity定義了不同的taskAffinity屬性。

(2)、Activity物件的複用

啟動一個Activity時,如果系統的後臺Task已經有一個該Activity的例項存在,那麼系統會再建立一個新的Activity例項,還是將已經存在的Activity例項切換到前臺呢? 答案是:都有可能。有很多因素可以影響結果,包括Activity的屬性值以及Intent中指定的標誌。我們先看看Activity的屬性launcherMode會有哪些影響:

  • standard模式:standard模式下的Activity每次啟動時都會建立該Activity的例項物件;同一個Task中可以同時存在該Activity的多個例項;一個Activity的多個例項可以出現在多個Task棧中。
  • singleTop模式:如果設定為singleTop模式的Activity例項位於Task的棧頂,則不會建立一個新的物件,但是該Activity物件切換到前臺時,它的onNewIntent()方法將會被呼叫,新的intent透過這種方式傳遞給例項物件。如果Activity不在其Task的棧頂,就和standard模式一樣,會建立新的例項物件。
  • singleTask模式:設定為singleTask模式的Activity具有系統唯一性,只能在系統中建立該Activity的一個例項物件。啟動設定為singleTask的Activity時,如果系統中已經存在該Activity的例項,則將其所在的Task排在它前面的Activity都出棧,將該Activity帶到棧頂,並呼叫onNewIntent()方法,將新的intent傳遞給該例項。如果該Activity在系統中還沒有例項物件,就會建立一個該Activity的例項物件,如果該Activity的taskAffinity屬性值和當前Task的affinity值相同,它會加入到當前TAsk中,否則,即使啟動該Activity的Intent中沒有指定FLAG_ACTIVITYNEWTASK標誌,也會啟動新的Task,將Activity置於其中。
  • singleInstance模式:設定為singleInstance模式的Activity同樣具有系統唯一性,系統中只有該Activity的一個例項物件。同時Activity位於一個單獨的Task中,該Task中也只有一個Activity。
  • allowTaskReparenting屬性:通常情況下,一個Activity建立出來後,會停留在某個Task中,直到它被銷燬。但是如果Activity的allowTaskReparenting屬性設定為true,則該Activity可以在不同的Task之間轉移。但是,這個屬性只有在啟動Activity的Intent中設定了FLAGACTIVITYRESETTASKIF_NEEDED標誌時才起作用。
  • allowRetainTaskState屬性:預設情況下,如果一個Task位於後臺的時間太長,系統會清理該Task中的Activity,除了最初啟動的Task的Activity以外,其他的Activity都會被系統銷燬。如果應用希望保留這些Activity,可以將啟動Task的Activity的allowRetainTaskState屬性設定為true。
  • clearTaskOnLaunch屬性:前面介紹了,當使用帶有標誌FLAG_ACTIVITYNEWTASK的Intent啟動一個Activity時,如果該Acitivty位於一個Task中,會將Task整體帶到前臺,其中Activity保持不變。但是如果該Activity啟動的是Task的根Activity(root Activity),同時該Activity的屬性clearTaskOnLaunch設定為true,那麼系統出了將Task帶到前臺外,還會清除除了root Activity以外的所有Activity。因此,這個屬性的作用相當於每次銷燬Task,然後重新開始一個。
  • finishOnTaskLaunch屬性:設定為true,系統將會銷燬該Activity,然後重新再啟動一個。

除了FLAGACTIVITYNEWTASK標誌以外,Intent中還定義幾個和Activity相關的標誌: - FLAGACTIVITYCLEARTOP:如果啟動的Activity已經存在,則把該Activity帶到前臺,並把它前面的Activity都出棧。 - FLAGACTIVITYBROUGHTTOFRONT:如果啟動的Activity已經存在,則把該Activity帶到前臺,但是不關閉它前面的Activity。 - FLAG_ACTIVITYSINGLETOP:如果啟動的Activity已經位於Task的棧頂,則不會建立一個新的Activity,而是把該Activity帶到前臺。

Android開發——Intent中的各種FLAG http://blog.csdn.net/javensun/article/details/8700265

6、Activity的啟動流程

(1)、startActivityMayWait()方法

AMS中提供了幾個介面來啟動Activity,但是,它們都會呼叫ActivityStackSupervisor類的startActivityMayWait()方法。

程式碼語言:javascript
複製
public final class ActivityStackSupervisor implements DisplayListener {
   ......
   final int startActivityMayWait(IApplicationThread caller, int callingUid,
               String callingPackage, Intent intent, String resolvedType,
               IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
               IBinder resultTo, String resultWho, int requestCode, int startFlags,
               ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
               Bundle options, boolean ignoreTargetSecurity, int userId,
               IActivityContainer iContainer, TaskRecord inTask) {
       ......
       intent = new Intent(intent);  // 建立一個新的Intent物件,方便改動
       ActivityInfo aInfo =      // 獲取即將啟動的Activity的資訊
               resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
       ......
       // 呼叫startActivityLocked方法
       int res = startActivityLocked(caller, intent, resolvedType, aInfo,
                   voiceSession, voiceInteractor, resultTo, resultWho,
                   requestCode, callingPid, callingUid, callingPackage,
                   realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
                   componentSpecified, null, container, inTask);
       ...
       if (outResult != null) {
            outResult.result = res;
            if (res == ActivityManager.START_SUCCESS) {
                mWaitingActivityLaunched.add(outResult);
                do {
                    try {
                        mService.wait();  // 等待應用程序中Activity的啟動完成
                    } catch (InterruptedException e) {
                    }
                } while (!outResult.timeout && outResult.who == null);
            }
           ......    
       }
       ......
       return res;
   }
}

startActivityMayWait()首先呼叫resolveActivity()方法獲取需要啟動的Activity的資訊,resolveActivity()方法透過呼叫PackageManagerService的resolveIntent()方法來獲取Activity的資訊,得到Activity的資訊後,繼續呼叫startActivityLocked()方法來繼續啟動Activity。如果啟動Activity的應用需要返回結果,則呼叫mService物件的wait()方法掛起執行緒等待啟動的結果。

(2)、startActivityLocked()方法
程式碼語言:javascript
複製
final int startActivityLocked(IApplicationThread caller,
           Intent intent, String resolvedType, ActivityInfo aInfo,
           IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
           IBinder resultTo, String resultWho, int requestCode,
           int callingPid, int callingUid, String callingPackage,
           int realCallingPid, int realCallingUid, int startFlags, Bundle options,
           boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
           ActivityContainer container, TaskRecord inTask) {
   ......
   int err = ActivityManager.START_SUCCESS;
   ProcessRecord callerApp = null;
   if (caller != null) {
       callerApp = mService.getRecordForAppLocked(caller);  // 得到呼叫程序的資訊
       ...
   }
   ...... // 錯誤檢查
   final int startAnyPerm = mService.checkPermission(
               START_ANY_ACTIVITY, callingPid, callingUid);   // 檢查呼叫者許可權
   ......
   // 建立ActivityRecord物件
   ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
               intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
               requestCode, componentSpecified, voiceSession != null, this, container, options);
  ......
  // 繼續呼叫startActivityUncheckedLocked啟動當前的Activity
  err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
               startFlags, true, options, inTask);  
  ......
  return err;                    
}

startActivityLocked()方法首先進行了各種錯誤檢查,接著檢查呼叫者的許可權,以及Intent防火牆是否遮蔽了該Intent(規則是透過/data/system/ifw目錄下的檔案設定的)。完成所有檢查後,穿件一個ActivityRecord物件,並呼叫getFocusedStack()方法來獲取當前具有使用者輸入焦點的ActivityStack。

(3)、startActivityUncheckedLocked()方法
程式碼語言:javascript
複製
final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,
           IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
           boolean doResume, Bundle options, TaskRecord inTask) {
   // r:即將啟動的Activity
   // sourceRecord:啟動r的源Activity,有可能為null
   ......
   targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);
   if (!launchTaskBehind) {
       mService.setFocusedActivityLocked(r, "startedActivity");
   }
   return ActivityManager.START_SUCCESS;
}

startActivityUncheckedLocked()方法的程式碼非常長,主要是透過判斷Intent的標誌和Activity的屬性來確定Activity的Task,對於處理的細節我們就不分析了。有興趣的童鞋可以結合上一屆的內容自行分析。方法中找到包含Activity的Task後,呼叫ActivityStack的startActivityLocked()方法繼續啟動。

(4)、startActivityLocked()方法
程式碼語言:javascript
複製
final void startActivityLocked(ActivityRecord r, boolean newTask,
           boolean doResume, boolean keepCurTransition, Bundle options) {
     TaskRecord rTask = r.task;
     final int taskId = rTask.taskId;
     if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
         insertTaskAtTop(rTask, r);  // 如果是新的Task,就把它放在頂部
         mWindowManager.moveTaskToTop(taskId);
     }
     TaskRecord task = null;
     if (!newTask) {   // 如果不需要啟動新的Task
         boolean startIt = true;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             task = mTaskHistory.get(taskNdx);
             if (task.getTopActivity() == null) {
                 continue;
             }
             if (task == r.task) {
                 ......
                 break;  // 找到了Task,跳出迴圈
             } ...
         }
     }     if (task == r.task && mTaskHistory.indexOf(task) != (mTaskHistory.size() - 1)) {
         mStackSupervisor.mUserLeaving = false;
     }
     task = r.task;
     task.addActivityToTop(r);  // 把Activity放到已找到Task的頂部
     task.setFrontOfTask();
     r.putInHistory();     // 呼叫WindowManagerService中的方法準備繪製Activity以及切換Activity動畫
     if (!isHomeStack() || numActivities() > 0) {
        // 如果不是Home應用的Stack或者Stack中有Activity,
        ......
     } else {
         mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                 r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                 (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
                 r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
         ActivityOptions.abort(options);
         options = null;
     }
     if (VALIDATE_TOKENS) {
         validateAppTokensLocked();
     }     if (doResume) {
         // 啟動Activity
         mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
     }
 }

這裡呼叫了WindowManagerService的方法處理Activity的顯示和切換動畫。WMS以後由視覺化組的同事講解哈。

ActivityStack類的startActivityLocked()方法相對比較簡單,就是講ActivityRecord物件加入到Task的頂部,同時把Task也放到mHistoryStack列表的頂部。方法的最後透過mStackSupervisor物件的resumeTopActivitiesLocked方法來顯示位於Task棧頂的Activity,這個方法在AMS中經常會被呼叫,主要作用是將位於棧頂的Activity顯示出來。這時,當前的Activity(mResumedActivity物件引用)還顯示在螢幕上。它最終會呼叫ActivityStack類的resumeTopActivityInnerLocked()方法,下來我們直接分析它的執行過程。

(5)、resumeTopActivityInnerLocked()方法
程式碼語言:javascript
複製
private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
   ......
   final ActivityRecord next = topRunningActivityLocked(null); // next表示即將啟動的Activity
   if (next == null) {
       ...... // 如果當前Task沒有Activity,顯示Home Activity
       return isOnHomeDisplay() &&  
                   mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason);
   }
   ...
   if (mResumedActivity == next && next.state == ActivityState.RESUMED &&
                   mStackSupervisor.allResumedActivitiesComplete()) {
      ...
      return false;  // 如果當前的Activity就是要啟動的Activity,直接返回
   }
   ......
   if (mService.isSleepingOrShuttingDown()
               && mLastPausedActivity == next
               && mStackSupervisor.allPausedActivitiesComplete()) {
       ...
       return false;  // 如果系統正在睡眠或關閉,直接退出
   }
   ......
   if (prev != null && prev != next) {
      if (!mStackSupervisor.mWaitingVisibleActivities.contains(prev)
               && next != null && !next.nowVisible) {
           // 如果Activity還不可見,把前一個Activity加入mWaitingVisibleActivities列表
           mStackSupervisor.mWaitingVisibleActivities.add(prev);
       } else {
           if (prev.finishing) {  // 如果Activity已經是visible狀態,把前一個Activity隱藏起來
               mWindowManager.setAppVisibility(prev.appToken, false);
           }
       }
   }
   ......
   boolean anim = true;
   ......  // 呼叫WindowManagerService的方法處理Activity的顯示
   ActivityStack lastStack = mStackSupervisor.getLastStack();
   if (next.app != null && next.app.thread != null) {
       // 如果Activity的應用程序已存在,只需要把Activity顯示出來即可
       mWindowManager.setAppVisibility(next.appToken, true);
       ......
       try {
           ArrayList<ResultInfo> a = next.results;
           if (a != null) {  // 如果Activity中海油等待返回的結果,先傳送結果
               final int N = a.size();
               if (!next.finishing && N > 0) {
                   next.app.thread.scheduleSendResult(next.appToken, a);
               }
           }
           if (next.newIntents != null) {  
               // 如果滿足前面介紹的幾種重啟Activity的情況,呼叫onNewIntent方法
               next.app.thread.scheduleNewIntent(next.newIntents, next.appToken);
           }
           ...
           // 呼叫應用程序Activity的onResume流程
           next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
                       mService.isNextTransitionForward(), resumeAnimOptions);
           mStackSupervisor.checkReadyForSleepLocked();
       } catch (Exception e) {
           ......
           return true;
       }
   } else {
       // 如果Activity所在的應用程序還沒有啟動,就先啟動應用
       ......
       mStackSupervisor.startSpecificActivityLocked(next, true, true);
   }
   return true;
}

resumeTopActivityInnerLocked()方法雖然很長,但是去掉log和一些不太重要的分支程式碼後,整個邏輯還是比較清晰的,上面註釋已經很詳細了,這裡不再重複。如果Activity所在的應用已經啟動,這裡將會呼叫應用程序的scheduleResumeActivity()方法,最終會回撥ActivityThread執行Activity的onResume()方法。如果應用還沒有啟動,或者剛啟動,則呼叫startSpecificActivityLocked()方法繼續處理。

(6)、startSpecificActivityLocked()方法
程式碼語言:javascript
複製
void startSpecificActivityLocked(ActivityRecord r,
           boolean andResume, boolean checkConfig) {
   ProcessRecord app = mService.getProcessRecordLocked(r.processName,
           r.info.applicationInfo.uid, true);
   r.task.stack.setLaunchTime(r);
   if (app != null && app.thread != null) { // 判斷應用程序是否已啟動
       try {
           if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                   || !"android".equals(r.info.packageName)) {
               app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
                       mService.mProcessStats);
           }
           realStartActivityLocked(r, app, andResume, checkConfig);
           return;
       } catch (RemoteException e) {
           Slog.w(TAG, "Exception when starting activity "
                   + r.intent.getComponent().flattenToShortString(), e);
       }
   }
   // 啟動應用程序
   mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
           "activity", r.intent.getComponent(), false, false, true);
}

startSpecificActivityLocked()方法中如果發現應用程序沒有啟動,則呼叫AMS的startProcessLocked()方法來啟動程序(前面章節已經介紹過),否者呼叫realStartActivityLocked()方法繼續執行。realStartActivityLocked()比較長,這裡就不分析了,它最後呼叫到應用程序中的scheduleLaunchActivity()方法,從而回撥Activity的onCreate流程。

7、框架圖

透過上面AMS啟動Activity所呼叫到的方法分析,這樣Activity啟動中涉及的所有回撥介面我們都找到哦啊了呼叫的地方,整個流程也就非常清晰。

分析Activity啟動流程,無非就是牢牢把握住兩條主線:應用程序(ActivityThread)和服務端程序(AMS)

深入理解Activity啟動流程和AMS框架(三)

相關文章