Android應用程式內部啟動Activity過程 startActivity 的原始碼分析
上文介紹了Android應用程式的啟動過程,即應用程式預設Activity的啟動過程,一般來說,這種預設Activity是在新的程序和任務中啟動的;本文將繼續分析在應用程式內部啟動非預設Activity的過程的原始碼,這種非預設Activity一般是在原來的程序和任務中啟動的。
《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!
在應用程式內部啟動非預設Activity的過程與在應用程式啟動器Launcher中啟動另外一個應用程式的預設Activity的過程大體上一致的,因此,這裡不會像上文Android應用程式啟動過程原始碼分析
回憶一下Android應用程式的Activity啟動過程簡要介紹和學習計劃一文所用的應用程式Activity,它包含兩個Activity,分別是MainActivity和SubActivity,前者是應用程式的預設Activity,後者是非預設Activity。MainActivity啟動起來,通過點選它介面上的按鈕,便可以在應用程式內部啟動SubActivity。
我們先來看一下應用程式的配置檔案AndroidManifest.xml,看看這兩個Activity是如何配置的:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="shy.luo.activity" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name =".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SubActivity" android:label="@string/sub_activity"> <intent-filter> <action android:name="shy.luo.activity.subactivity"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> </application> </manifest>
這裡可以很清楚地看到,MainActivity被配置成了應用程式的預設Activity,而SubActivity可以通過名稱“shy.luo.activity.subactivity”隱式地啟動,我們來看一下src/shy/luo/activity/MainActivity.java檔案的內容,可以清楚地看到SubActivity是如何隱式地啟動的:public class MainActivity extends Activity implements OnClickListener { ...... @Override public void onClick(View v) { if(v.equals(startButton)) { Intent intent = new Intent("shy.luo.activity.subactivity"); startActivity(intent); } } }
這裡,首先建立一個名稱為“shy.luo.activity.subactivity”的Intent,然後以這個Intent為引數,通過呼叫startActivity函式來實現隱式地啟動SubActivity。有了這些背景知識後,我們就來看一下SubActivity啟動過程的序列圖:
Step 1. Activity.startActivity
這一步與上一篇文章Android應用程式啟動過程原始碼分析的Step 2大體一致,通過指定名稱“shy.luo.activity.subactivity”來告訴應用程式框架層,它要隱式地啟動SubActivity。所不同的是傳入的引數intent沒有Intent.FLAG_ACTIVITY_NEW_TASK標誌,表示這個SubActivity和啟動它的MainActivity執行在同一個Task中。
Step 2. Activity.startActivityForResult
Step 3. Instrumentation.execStartActivity
Step 4. ActivityManagerProxy.startActivity
Step 5. ActivityManagerService.startActivity
Step 6. ActivityStack.startActivityMayWait
Step 7. ActivityStack.startActivityLocked
Step 8. ActivityStack.startActivityUncheckedLocked
這一步與上一篇文章Android應用程式啟動過程原始碼分析的Step 9有所不同,主要是當前要啟動的Activity與啟動它的Activity是在同一個Task中執行的,我們來詳細看一下。這個函式定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java檔案中:
public class ActivityStack { ...... final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, Uri[] grantedUriPermissions, int grantedMode, boolean onlyIfNeeded, boolean doResume) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; int launchFlags = intent.getFlags(); ...... if (sourceRecord == null) { ...... } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { ...... } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE ...... } if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { ...... } boolean addingToTask = false; if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { ...... } if (r.packageName != null) { // If the activity being launched is the same as the one currently // at the top, then we need to check if it should only be launched // once. ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { if (top.realActivity.equals(r.realActivity)) { ...... } } } else { ...... } boolean newTask = false; // Should this be considered a new task? if (r.resultTo == null && !addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { ...... } else if (sourceRecord != null) { ...... // An existing activity is starting this new activity, so we want // to keep the new one in the same task as the one that is starting // it. r.task = sourceRecord.task; ...... } else { ...... } ...... startActivityLocked(r, newTask, doResume); return START_SUCCESS; } ......}
這裡,引數intent的標誌位Intent.FLAG_ACTIVITY_NEW_TASK沒有設定,在配置檔案AndriodManifest.xml中,SubActivity也沒有配置啟動模式launchMode,於是它就預設標準模式,即ActivityInfo.LAUNCH_MULTIPLE,因此,下面if語句不會執行: if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { ...... }
於是,變數addingToTask為false。繼續往下看:
if (r.packageName != null) { // If the activity being launched is the same as the one currently // at the top, then we need to check if it should only be launched // once. ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { if (top.realActivity.equals(r.realActivity)) { ...... } } }
這裡看一下當前要啟動的Activity是否就是當前堆疊頂端的Activity,如果是的話,在某些情況下,就不用再重新啟動了。函式topRunningNonDelayedActivityLocked返回當前堆疊頂端的Activity,這裡即為MainActivity,而當前要啟動的Activity為SubActivity,因此,這二者不相等,於是跳過裡面的if語句。接著往下執行:
// Should this be considered a new task? if (r.resultTo == null && !addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { ...... } else if (sourceRecord != null) { ...... // An existing activity is starting this new activity, so we want // to keep the new one in the same task as the one that is starting // it. r.task = sourceRecord.task; ...... } else { ...... }
前面說過引數intent的標誌位Intent.FLAG_ACTIVITY_NEW_TASK沒有設定,而這裡的sourceRecord即為當前執行啟動Activity操作的Activity,這裡即為MainActivity,因此,它不為null,於是於MainActivity所屬的Task設定到r.task中去,這裡的r即為SubActivity。看到這裡,我們就知道SubActivity要和MainActivity執行在同一個Task中了,同時,變數newTask的值為false。最後,函式進 入startActivityLocked(r, newTask, doResume)進一步處理了。這個函式同樣是定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java檔案中:
public class ActivityStack { ...... private final void startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume) { final int NH = mHistory.size(); int addPos = -1; if (!newTask) { // If starting in an existing task, find where that is... boolean startIt = true; for (int i = NH-1; i >= 0; i--) { ActivityRecord p = (ActivityRecord)mHistory.get(i); if (p.finishing) { continue; } if (p.task == r.task) { // Here it is! Now, if this is not yet visible to the // user, then just add it without starting; it will // get started when the user navigates back to it. addPos = i+1; if (!startIt) { mHistory.add(addPos, r); r.inHistory = true; r.task.numActivities++; mService.mWindowManager.addAppToken(addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); if (VALIDATE_TOKENS) { mService.mWindowManager.validateAppTokens(mHistory); } return; } break; } if (p.fullscreen) { startIt = false; } } } ...... // Slot the activity into the history stack and proceed mHistory.add(addPos, r); r.inHistory = true; r.frontOfTask = newTask; r.task.numActivities++; ...... if (doResume) { resumeTopActivityLocked(null); } } ......}
這裡傳進來的引數newTask為false,doResume為true。當newTask為false,表示即將要啟動的Activity是在原有的Task執行時,如果這個原有的Task當前對使用者不可見時,這時候就不需要繼續執行下去了,因為即使把這個Activity啟動起來,使用者也看不到,還不如先把它儲存起來,等到下次這個Task對使用者可見的時候,再啟動不遲。這裡,這個原有的Task,即執行MainActivity的Task當前對使用者是可見的,因此,會繼續往下執行。接下去執行就會把這個SubActivity通過mHistroy.add(addPos, r)新增到堆疊頂端去,然後呼叫resumeTopActivityLocked進一步操作。
Step 9. ActivityStack.resumeTopActivityLocked
但是要注意的是,執行到這個函式的時候,當前處於堆疊頂端的Activity為SubActivity,ActivityStack的成員變數mResumedActivity指向MainActivity。 Step 10. ActivityStack.startPausingLocked 這一步與上一篇文章Android應用程式啟動過程原始碼分析的Step 11一致。
從這裡開始,ActivityManagerService通知MainActivity進入Paused狀態。
Step 11. ApplicationThreadProxy.schedulePauseActivity
Step 12. ApplicationThread.schedulePauseActivity
Step 13. ActivityThread.queueOrSendMessage 這一步與上一篇文章Android應用程式啟動過程原始碼分析的Step 14一致。
Step 14. H.handleMessage
Step 15. ActivityThread.handlePauseActivity
Step 16. ActivityManagerProxy.activityPaused
Step 17. ActivityManagerService.activityPaused
Step 18. ActivityStack.activityPaused
Step 19. ActivityStack.completePauseLocked
執行到這裡的時候,MainActivity就進入Paused狀態了,下面就開始要啟動SubActivity了。
Step 20. ActivityStack.resumeTopActivityLokced
Step 21. ActivityStack.startSpecificActivityLocked
這一步與上一篇文章Android應用程式啟動過程原始碼分析的Step 22就有所不同了,這裡,它不會呼叫mService.startProcessLocked來建立一個新的程序來啟動新的Activity,我們來看一下這個函式的實現,這個函式定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java檔案中:
public class ActivityStack { ...... private final void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application already running? ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid); ...... if (app != null && app.thread != null) { try { realStartActivityLocked(r, app, andResume, checkConfig); return; } catch (RemoteException e) { ...... } } ...... } ......}
這裡由於不是第一次啟動應用程式的Activity(MainActivity是這個應用程式第一個啟動的Activity),所以下面語句: ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid);
取回來的app不為null。在上一篇文章Android應用程式啟動過程原始碼分析中,我們介紹過,在Activity應用程式中的AndroidManifest.xml配置檔案中,我們沒有指定application標籤的process屬性,於是系統就會預設使用package的名稱,這裡就是"shy.luo.activity"了。每一個應用程式都有自己的uid,因此,這裡uid + process的組合就可以建立一個全域性唯一的ProcessRecord。這個ProcessRecord是在前面啟動MainActivity時建立的,因此,這裡將它取回來,並儲存在變數app中。注意,我們也可以在AndroidManifest.xml配置檔案中指定SubActivity的process屬性值,這樣SubActivity就可以在另外一個程序中啟動,不過很少有應用程式會這樣做,我們不考慮這種情況。這個app的thread也是在前面啟動MainActivity時建立好的,於是,這裡就直接呼叫realStartActivityLocked函式來啟動新的Activity了,新的Activity的相關資訊都儲存在引數r中了。
Step 22. ActivityStack.realStartActivityLocked
這一步與上一篇文章Android應用程式啟動過程原始碼分析的Step 28一致。 Step 23. ApplicationThreadProxy.scheduleLaunchActivity
Step 24. ApplicationThread.scheduleLaunchActivity
Step 25. ActivityThread.queueOrSendMessage
Step 26. H.handleMessage
Step 27. ActivityThread.handleLaunchActivity
Step 28. ActivityThread.performLaunchActivity
這一步與上一篇文章Android應用程式啟動過程原始碼分析的Step 34一致,不過,這裡要從ClassLoader裡面載入的類就是shy.luo.activity.SubActivity了。
Step 29. SubAcitiviy.onCreate
這個函式定義在packages/experimental/Activity/src/shy/luo/activity/SubActivity.java檔案中,這是我們自定義的app工程檔案:
public class SubActivity extends Activity implements OnClickListener { ...... @Override public void onCreate(Bundle savedInstanceState) { ...... Log.i(LOG_TAG, "Sub Activity Created."); } ......}
這樣,SubActivity就在應用程式Activity內部啟動起來了。 在應用程式內部啟動新的Activity的過程要執行很多步驟,但是整體來看,主要分為以下四個階段:一. Step 1 - Step 10:應用程式的MainActivity通過Binder程序間通訊機制通知ActivityManagerService,它要啟動一個新的Activity; 二. Step 11 - Step 15:ActivityManagerService通過Binder程序間通訊機制通知MainActivity進入Paused狀態; 三. Step 16 - Step 22:MainActivity通過Binder程序間通訊機制通知ActivityManagerService,它已經準備就緒進入Paused狀態,於是ActivityManagerService就準備要在MainActivity所在的程序和任務中啟動新的Activity了; 四. Step 23 - Step 29:ActivityManagerService通過Binder程序間通訊機制通知MainActivity所在的ActivityThread,現在一切準備就緒,它可以真正執行Activity的啟動操作了。
和上一篇文章Android應用程式啟動過程原始碼分析中啟動應用程式的預設Activity相比,這裡在應用程式內部啟動新的Activity的過程少了中間建立新的程序這一步,這是因為新的Activity是在已有的程序和任務中執行的,無須建立新的程序和任務。
這裡希望讀者能夠把本文和上文Android應用程式啟動過程原始碼分析仔細比較一下應用程式的預設Activity和非預設Activity啟動過程的不同之處,以加深對Activity的理解。
最後,在本文和上文中,我們多次提到了Android應用程式中任務(Task)的概念,它既不是我們在Linux系統中所理解的程序(Process),也不是執行緒(Thread),它是使用者為了完成某個目標而需要執行的一系列操作的過程的一種抽象。這是一個非常重要的概念,它從使用者體驗的角度出發,界定了應用程式的邊界,極大地方便了開發者利用現成的元件(Activity)來搭建自己的應用程式,就像搭積木一樣,而且,它還為應用程式遮蔽了底層的程序,即一個任務中的Activity可以都是執行在同一個程序中,也中可以執行在不同的程序中。Android應用程式中的任務的概念,具體可以參考官方文件http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html,上面已經介紹的非常清楚了。