Activity 啟動(dòng)流程學(xué)習(xí)總結(jié)(附源碼)

前言

關(guān)于 Activity 啟動(dòng),Android 中場(chǎng)景大致有兩個(gè):

  1. 從 launcher 中啟動(dòng)應(yīng)用,觸發(fā)該應(yīng)用默認(rèn) Activity 的啟動(dòng)。這種 Activity 都是在新進(jìn)程和新的任務(wù)棧中啟動(dòng)的,所以涉及到新進(jìn)程和新任務(wù)棧的初始化
  2. 應(yīng)用程序內(nèi)部啟動(dòng)非默認(rèn) Activity, 被啟動(dòng)的 Activity 一般在原來(lái)的進(jìn)程和任務(wù)棧中啟動(dòng)

本文主要介紹第一種場(chǎng)景

背景知識(shí)

進(jìn)程與線程

由于 Activity 的啟動(dòng)流程中涉及了大量的進(jìn)程間通信芯砸,例如:ActivityManagerService 和 ActivityStack 位于同一進(jìn)程,ApplicationThread 和 ActivityThread 位于同一進(jìn)程给梅。所以有必要梳理一下進(jìn)程與線程的區(qū)別

從操作系統(tǒng)的角度看,進(jìn)程和線程有什么區(qū)別双揪?

  1. 進(jìn)程有獨(dú)立的地址空間动羽,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其他進(jìn)程造成影響渔期。
  2. 線程沒(méi)有獨(dú)立的地址空間运吓,線程只是進(jìn)程所屬進(jìn)程的不同執(zhí)行路徑

從 JVM 的運(yùn)行時(shí)數(shù)據(jù)分區(qū)的角度,進(jìn)程和線程有什么關(guān)系疯趟?

JVM 運(yùn)行時(shí)數(shù)據(jù)分區(qū)如圖所示:

image

由圖可以看出如下幾點(diǎn):

  1. 一個(gè)進(jìn)程中的多個(gè)線程共享堆區(qū)和方法區(qū)
  2. 每個(gè)線程擁有自己的程序計(jì)數(shù)器拘哨,虛擬機(jī)棧,本地方法棧

Activity 啟動(dòng)模式

啟動(dòng)模式橫向?qū)Ρ?/h4>

四中啟動(dòng)模式的概念就不詳述了信峻,這里只是對(duì)關(guān)鍵點(diǎn)做出橫向?qū)Ρ染肭啵鐖D所示

Activity 啟動(dòng)模式比較

一些鮮為人知的 Intent Flag

下面是一些 Intent Flag 及介紹,如圖所示

鮮為人知但常用的啟動(dòng)模式 flag

Activity 啟動(dòng)流程源碼分析

概念

首先介紹一下 Activity 啟動(dòng)流程涉及到的核心類

ActivityThread

ActivityThread 的作用是管理應(yīng)用程序主線程的相關(guān)流程盹舞,例如管理與處理 activity manager 發(fā)送的請(qǐng)求产镐,這些請(qǐng)求可以來(lái)自 Activity 或 BroadCast

Instrumentation

它用來(lái)監(jiān)控應(yīng)用程序與系統(tǒng)之間的交互

ActivityManagerService (AMS)

AMS(ActivityManagerService)是貫穿Android系統(tǒng)組件的核心服務(wù),負(fù)責(zé)了系統(tǒng)中四大組件的啟動(dòng)踢步、切換癣亚、調(diào)度以及應(yīng)用進(jìn)程管理和調(diào)度工作

執(zhí)行流程

Step 1. Launcher.startActivitySafely

/**
 * Launches the intent referred by the clicked shortcut.
 *
 * @param v The view representing the clicked shortcut.
 */
public void onClick(View v) {
    // Make sure that rogue clicks don't get through while allapps is launching, or after the
    // view has detached (it's possible for this to happen if the view is removed mid touch).
    if (v.getWindowToken() == null) {
        return;
    }
    if (!mWorkspace.isFinishedSwitchingState()) { // Launcher 程序在監(jiān)聽(tīng)點(diǎn)擊事件時(shí)會(huì)判斷頁(yè)面是否正在滑動(dòng),如果在滑動(dòng)則不響應(yīng)點(diǎn)擊應(yīng)用程序 icon 的事件
        return;
    }
    Object tag = v.getTag();
    if (tag instanceof ShortcutInfo) { // 處理點(diǎn)擊應(yīng)用程序 icon 的場(chǎng)景
        // Open shortcut
        final Intent intent = ((ShortcutInfo) tag).intent;
        int[] pos = new int[2];
        v.getLocationOnScreen(pos);
        intent.setSourceBounds(new Rect(pos[0], pos[1],
                pos[0] + v.getWidth(), pos[1] + v.getHeight()));

        boolean success = startActivitySafely(v, intent, tag); // startActivitySafely 這里主要會(huì)判斷待啟動(dòng)的 Activity 是否存在获印,若不存在則會(huì)報(bào) ActivityNotFound Exception 并捕獲

        if (success && v instanceof BubbleTextView) {
            mWaitingForResume = (BubbleTextView) v;
            mWaitingForResume.setStayPressed(true);
        }
    } else if (tag instanceof FolderInfo) {  // 處理點(diǎn)擊文件夾的場(chǎng)景
        ···
    } else if (v == mAllAppsButton) {
        ···
    }
}

Launcher 程序在監(jiān)聽(tīng)點(diǎn)擊事件時(shí)會(huì)判斷頁(yè)面是否正在滑動(dòng)述雾,如果在滑動(dòng)則不響應(yīng)點(diǎn)擊應(yīng)用程序 icon 的事件

Step 2. Activity.startActivity()

由于 Launcher 繼承于 Activity, 因此代碼執(zhí)行流程到了 startActivity()


public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks {
 
    ......
 
    @Override
    public void startActivity(Intent intent) {
        startActivityForResult(intent, -1);
    }
 
    ......

startActivity 最終是調(diào)用 startActivityForResult, 傳入的參數(shù)為 -1 代表這次啟動(dòng) Activity 不需要獲取結(jié)果

Step 3. Activity.startActivityForResult()


public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks {
 
    ......
 
    public void startActivityForResult(Intent intent, int requestCode) {
        if (mParent == null) {
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode);
            ......
        } else {
            ......
        }
 
 
    ......

這里有兩個(gè)有趣的場(chǎng)景:

  1. startActivityForResult() 不能用于啟動(dòng) singleTask 為啟動(dòng)模式的 Activity, 否則會(huì)從 onActivityForResult() 中收到 cancel 事件
  2. 假設(shè)有這樣一個(gè)場(chǎng)景,Activity A 啟動(dòng) Activity B, 如果在 A 的 onCreate() / onResume() 中調(diào)用 startActivityForResult(), 并且傳入的 requestCode >= 1, 那么 A Activity 所掛鉤的 Window 將暫時(shí)不可見(jiàn)玻孟,這種不可見(jiàn)狀態(tài)直到收到 B Activity 的回調(diào)結(jié)果唆缴。目的是防止 Activity 跳轉(zhuǎn)時(shí) UI 閃爍

Step 4. ActivityStack.startActivityUncheckedLocked

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();
 
        // We'll invoke onUserLeaving before onPause only if the launching
        // activity did not explicitly state that this is an automated launch.
        mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
        
        ......
 
        ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
            != 0 ? r : null;
 
        // If the onlyIfNeeded flag is set, then we can do this if the activity
        // being launched is the same as the one making the call...  or, as
        // a special case, if we do not know the caller then we count the
        // current top activity as the caller.
        if (onlyIfNeeded) {
            ......
        }
 
        if (sourceRecord == null) {
            ......
        } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
            ......
        } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
            ......
        }
 
        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 bring to front is requested, and no result is requested, and
                // we can find a task that was started with this same
                // component, then instead of launching bring that one to the front.
                if (r.resultTo == null) {
                    // See if there is a task to bring to the front.  If this is
                    // a SINGLE_INSTANCE activity, there can be one and only one
                    // instance of it in the history, and it is always in its own
                    // unique task, so we do a special search.
                    ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
                        ? findTaskLocked(intent, r.info)
                        : findActivityLocked(intent, r.info);
                    if (taskTop != null) {
                        ......
                    }
                }
        }
 
        ......
 
        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) {
                // todo: should do better management of integers.
                mService.mCurTask++;
                if (mService.mCurTask <= 0) {
                    mService.mCurTask = 1;
                }
                r.task = new TaskRecord(mService.mCurTask, r.info, intent,
                    (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
                ......
                newTask = true;
                if (mMainStack) {
                    mService.addRecentTaskLocked(r.task);
                }
 
        } else if (sourceRecord != null) {
            ......
        } else {
            ......
        }
 
        ......
 
        startActivityLocked(r, newTask, doResume);
        return START_SUCCESS;
    }

由于我們是總 launcher 啟動(dòng) Activity, 因此 當(dāng)前的前臺(tái)任務(wù)棧棧頂是 launcher Activity, 因此需要?jiǎng)?chuàng)建一個(gè)新的 ActivityStack, 和 ActivityRecord 對(duì)象,將 ActivityRecord 放入其中取募,最終這個(gè)新創(chuàng)建的 ActivityStack 被保存到 AMS 中

Step 5. Activity.resumeTopActivityLocked

   // If the top activity is the resumed one, nothing to do.
    if (mResumedActivity == next && next.state == ActivityState.RESUMED) {
    ......
    }
 
    // If we are sleeping, and there is no resumed activity, and the top
    // activity is paused, well that is the state we want.
    if ((mService.mSleeping || mService.mShuttingDown)
    && mLastPausedActivity == next && next.state == ActivityState.PAUSED) {
    ......
    }

現(xiàn)在我們已經(jīng)準(zhǔn)備好了新的 ActivityStack 和新的 ActivityRecord, 然后我們就需要處理 launcher Activity 的生命周期琐谤,將其置為 pause 狀態(tài)。根據(jù)源碼玩敏,這里首先會(huì)檢查被啟動(dòng)的 Activity 是否在棧頂斗忌,是否就是啟動(dòng)者,如果是的話旺聚,就什么都不做织阳,如果都不滿足的話,就需要將目前棧頂?shù)?Activity 置為 Pause 狀態(tài)砰粹,在這之前唧躲,首先要檢查是否有正在 Pausing 的 Activity, 如果有的話launcher Activity 需要被掛起,

總結(jié)

由于 Activity 啟動(dòng)流程較復(fù)雜碱璃,剩余流程將不再詳述弄痹,總結(jié)如下圖所示:


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嵌器,隨后出現(xiàn)的幾起案子肛真,更是在濱河造成了極大的恐慌,老刑警劉巖爽航,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚓让,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡讥珍,警方通過(guò)查閱死者的電腦和手機(jī)历极,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)衷佃,“玉大人趟卸,你說(shuō)我怎么就攤上這事∈弦澹” “怎么了衰腌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)觅赊。 經(jīng)常有香客問(wèn)我右蕊,道長(zhǎng),這世上最難降的妖魔是什么吮螺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任饶囚,我火速辦了婚禮帕翻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萝风。我一直安慰自己嘀掸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布规惰。 她就那樣靜靜地躺著睬塌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歇万。 梳的紋絲不亂的頭發(fā)上揩晴,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音贪磺,去河邊找鬼硫兰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寒锚,可吹牛的內(nèi)容都是我干的劫映。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刹前,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泳赋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起喇喉,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祖今,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后轧飞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撒踪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年过咬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片制妄。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掸绞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耕捞,到底是詐尸還是另有隱情衔掸,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布俺抽,位于F島的核電站敞映,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏磷斧。R本人自食惡果不足惜振愿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一捷犹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冕末,春花似錦萍歉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至藻肄,卻和暖如春蔑舞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仅炊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工斗幼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抚垄。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓蜕窿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親呆馁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桐经,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350