天天看點

Android應用程式内部啟動Activity過程(startActivity)的源代碼分析

   上文介紹了Android應用程式的啟動過程,即應用程式預設Activity的啟動過程,一般來說,這種預設Activity是在新的程序和任務中啟動的;本文将繼續分析在應用程式内部啟動非預設Activity的過程的源代碼,這種非預設Activity一般是在原來的程序和任務中啟動的。

《Android系統源代碼情景分析》一書正在進擊的程式員網(http://0xcc0xcd.com)中連載,點選進入!

        這裡,我們像上一篇文章Android應用程式啟動過程源代碼分析一樣,采用再上一篇文章Android應用程式的Activity啟動過程簡要介紹和學習計劃所舉的例子來分析在應用程式内部啟動非預設Activity的過程。

        在應用程式内部啟動非預設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">  

                    <action android:name="shy.luo.activity.subactivity"/>  

                    <category android:name="android.intent.category.DEFAULT"/>  

        </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啟動過程的序列圖:

點選檢視大圖

       與前面介紹的MainActivity啟動過程相比,這裡少了中間建立新的程序的步驟;接下來,我們就詳細分析一下SubActivity與MainActivity啟動過程中有差别的地方,相同的地方請參考Android應用程式啟動過程源代碼分析一文。

       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

       這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 3一緻。

       Step 3. Instrumentation.execStartActivity

       這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 4一緻。

       Step 4. ActivityManagerProxy.startActivity

       這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 5一緻。

       Step 5. ActivityManagerService.startActivity

       這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 6一緻。

       Step 6. ActivityStack.startActivityMayWait

       這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 7一緻。

       Step 7. ActivityStack.startActivityLocked

       這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 8一緻。

       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;

            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檔案中:

        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

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 10一緻。

        但是要注意的是,執行到這個函數的時候,目前處于堆棧頂端的Activity為SubActivity,ActivityStack的成員變量mResumedActivity指向MainActivity。

        Step 10. ActivityStack.startPausingLocked

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 11一緻。

        從這裡開始,ActivityManagerService通知MainActivity進入Paused狀态。

        Step 11. ApplicationThreadProxy.schedulePauseActivity

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 12一緻。

        Step 12. ApplicationThread.schedulePauseActivity

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 13一緻。

        Step 13. ActivityThread.queueOrSendMessage

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 14一緻。

        Step 14. H.handleMessage

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 15一緻。

        Step 15. ActivityThread.handlePauseActivity

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 16一緻。

        Step 16. ActivityManagerProxy.activityPaused

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 17一緻。

        Step 17. ActivityManagerService.activityPaused

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 18一緻。

        Step 18. ActivityStack.activityPaused

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 19一緻。

        Step 19. ActivityStack.completePauseLocked

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 20一緻。

        執行到這裡的時候,MainActivity就進入Paused狀态了,下面就開始要啟動SubActivity了。

        Step 20. ActivityStack.resumeTopActivityLokced

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 21一緻。

        Step 21. ActivityStack.startSpecificActivityLocked

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 22就有所不同了,這裡,它不會調用mService.startProcessLocked來建立一個新的程序來啟動新的Activity,我們來看一下這個函數的實作,這個函數定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java檔案中:

        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

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 29一緻。

        Step 24. ApplicationThread.scheduleLaunchActivity

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 30一緻。

        Step 25. ActivityThread.queueOrSendMessage

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 31一緻。

        Step 26. H.handleMessage

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 32一緻。

        Step 27. ActivityThread.handleLaunchActivity

        這一步與上一篇文章Android應用程式啟動過程源代碼分析的Step 33一緻。

        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是在已有的程序和任務中執行的,無須建立新的程序和任務。

      這裡同樣不少地方涉及到了Binder程序間通信機制,相關資料請參考Android程序間通信(IPC)機制Binder簡要介紹和學習計劃一文。

      這裡希望讀者能夠把本文和上文Android應用程式啟動過程源代碼分析仔細比較一下應用程式的預設Activity和非預設Activity啟動過程的不同之處,以加深對Activity的了解。

      最後,在本文和上文中,我們多次提到了Android應用程式中任務(Task)的概念,它既不是我們在Linux系統中所了解的程序(Process),也不是線程(Thread),它是使用者為了完成某個目标而需要執行的一系列操作的過程的一種抽象。這是一個非常重要的概念,它從使用者體驗的角度出發,界定了應用程式的邊界,極大地友善了開發者利用現成的元件(Activity)來搭建自己的應用程式,就像搭積木一樣,而且,它還為應用程式屏蔽了底層的程序,即一個任務中的Activity可以都是運作在同一個程序中,也中可以運作在不同的程序中。Android應用程式中的任務的概念,具體可以參考官方文檔http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html,上面已經介紹的非常清楚了。

---------------------

作者:羅升陽