深刻源碼--Activity啓動模式問題探究

目錄


前言

寫這篇文章的時候剛好是2020年春節,全民抗擊肺炎病毒,今年不能出門拜年,只能在家老實呆着,也沒啥事來作,我就尋思着寫點東西,但願能幫助一點人,以爲不錯的話,能夠給個贊哦,哈哈哈~java

一.進入主題 思考問題

分析源碼以前呢,咱先本身先思考幾個問題,分析完成後,看能不能解決掉~app

  • 若兩個應用A和B,A中有兩個Activity,A1和A2,B中有一個Activity B1,這三個Activity都是SingleTask模式,那麼啓動順序是A1 -> B1 -> A2,返回是什麼?
  • 接着前面問題,若是A2是Standard模式 + FlAG_ACTIVITY_NEW_TASK呢?
  • FLAG_ACTIVITY_NEW_TASK 有什麼用?taskAffinity又是啥?

二.啓動模式源碼分析

啓動模式相關源碼都在ActivityStarter.java文件的startActivityUnchecked這個方法, 咱們拆解這個函數來分析一下:( Android 9.0 代碼爲例 )ide

注意下兩個很重要的屬性 mLaunchFlags,mLaunchMode,後面的源碼分析也主要圍繞着兩個屬性在討論函數

mLaunchFlags 包含啓動的flag,好比FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_CLEAR_TASK等,做用是規定了如何去啓動一個Activity。源碼分析

mLaunchMode 表示啓動模式,好比LAUNCH_SINGLE_INSTANCE,LAUNCH_SINGLE_TASK等。post

2.1 初始化工做

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity) {
    
	// 初始化 mLaunchFlags mLaunchMode
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);
        // 一.計算 mLaunchFlags
        computeLaunchingTaskFlags();
        //賦值 mSourceTask
        computeSourceStack();
        mIntent.setFlags(mLaunchFlags);
        ... ... ... 
複製代碼

看 computeLaunchingTaskFlags 方法this

private void computeLaunchingTaskFlags() {
	
        ... ... ...
        
	    // mSourceRecod 指的是 啓動者,(注意區別於 被啓動者 mStartActivity) mSourceRecord爲null,表示咱們不是從一個Activity來啓動的
	    // 多是從 一個Service 或者 ApplicationContext 來的
            if (mSourceRecord == null) {
                if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
             	    //mInTask mSourceRecord 都爲null, 表示 不是從一個Activity 去啓動另一個Activity,因此無論什麼 
		    //都加上 FLAG_ACTIVITY_NEW_TASK
                    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
                }
            } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
		// 若是 啓動者 本身是 SINGLE_INSTANCE , 那麼無論被啓動的Activity是什麼模式,mLaunchFlags 都加上 FLAG_ACTIVITY_NEW_TASK,
		// 這個新 Activity 須要運行在 本身的 棧內
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
		//若是launchMode是 SINGLE_INSTANCE 或者 SINGLE_TASK; mLaunchFlags 添加 FLAG_ACTIVITY_NEW_TASK
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            }
    }
複製代碼

2.1 小結一下

注意圖中有3個地方爲mLaunchFlags添加了FLAG_ACTIVITY_NEW_TASK spa

  • 若是啓動者mSourceRecord爲null,好比在一個Service啓動了一個Activity,那麼會爲mLaunchFlags增長FLAG_ACTIVITY_NEW_TASK
  • 若是啓動者mSourceRecord是一個 SingleInstance 類型的Activity,那麼被啓動者的mLaunchFlags就會加上 FLAG_ACTIVITY_NEW_TASK
  • 若是被啓動者mStartActivity是SINGLE_INSTANCE或者SINGLE_TASK 類型的Activity,被啓動者的mLaunchFlags都會加上FLAG_ACTIVITY_NEW_TASK

2.2 getResuableIntentActivity

這一部分仍是startActivityUnchecked方法的一個片斷,緊接着第一部分的源碼3d

//仍是在 startActivityUnchecked 裏面
        ... ... ...
        // 1. 爲SINGLE_INSTANCE查找能夠複用的Activity 
        // 2. 爲 只有FLAG_ACTIVITY_NEW_TASK而且沒有MULTI_TASK的
        // 3. SINGLE_TASK 查找能夠添加的棧
        ActivityRecord reusedActivity = getReusableIntentActivity();
        ... ... ...
複製代碼

咱們順便也跳出startActivityUnchecked方法,去看一看getResuableIntentActivity方法。code

private ActivityRecord getReusableIntentActivity() {

	//putIntoExistingTask爲true的條件
	//(1)當啓動模式爲SingleInstance;
	//(2)當啓動模式爲SingleTask;
	//(3)使用了Intent.FLAG_ACTIVITY_NEW_TASK標籤,而且沒有使用FLAG_ACTIVITY_MULTIPLE_TASK標籤
        boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
                (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);

        putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
        ActivityRecord intentActivity = null;
		
        if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
        
            ... ... ...
            
        } else if (putIntoExistingTask) { //putIntoExistingTask爲true時的策略
            if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {

		// SINGLE_INSTANCE 模式下去尋找,這裏目的是findActivityRecord
               intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
                       mStartActivity.isActivityTypeHome());
            } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                ... ...
            } else {
                //這裏要區別於singleInstance調用的方法!!!這裏目的是findTaskRecord
                // Otherwise find the best task to put the activity in.
                intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
            }
        }
        return intentActivity;
    }
複製代碼

2.2 小結一下

下圖表示的是三種狀況下putIntoExistingTask爲true

putIntoExistingTask爲true時,繼續看代碼咱們還能得出另一個結論:

mLaunchMode爲SingleInstance時,走mSupervisor.findActivityLocked;

其餘狀況下,好比咱們的mStartActivity是一個standard模式的Activity,且只加上了FLAG_ACTIVITY_NEW_TASK的flag,會走mSupervisor.findTaskLocked

這裏咱們能夠隱約猜出來出來,被啓動者爲SinglgeInstance的狀況下,咱們是尋找相等的ActivityRecord;而其餘狀況下,咱們找的是一個最合適的棧(從註釋也能夠看出來)。實際上咱們猜的很對,對於SingleInstance的狀況,源碼上也是遍歷查找相同的ActivityRecord。可是對於其餘狀況呢?咱們先思考一個問題,什麼叫最合適的棧?它須要知足什麼樣的條件?

2.3 最合適的可複用棧

先說一個預備的知識點。咱們的AMS如何管理咱們衆多的Activity的?

AMS如何管理衆多的Activity的?

從mSupervisor.findTaskLocked進入,咱們最後追蹤到ActivityStack.java

void findTaskLocked(ActivityRecord target, FindTaskResult result) {
        ... ... ...
        // 注意看這裏是倒序遍歷 mTaskHistory
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
        
            ... ... ...

            } else if (!isDocument && !taskIsDocument
                    && result.r == null && task.rootAffinity != null) {
                //檢查 是否是 相同的 taskAffinity
                if (task.rootAffinity.equals(target.taskAffinity)) {
                    //當咱們找到taskAffinity符合的棧以後,並無立馬break,而是繼續去尋找,說明task的index越小,表示更適合
                    result.r = r;
                    result.matchedByRootAffinity = true;
                }
            } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
        }
    }
複製代碼

2.3 小結一下

最合適的棧 知足兩個條件

1.Activity的taskAffinity和咱們的task的rootAffinity相等

2.不一樣的task的rootAffinity多是相等的,倒序遍歷找到index最小的,也是最合適的

2.4 reusedActivity的處理

咱們接着分析startActivityUnchecked代碼

// 三. 利用能夠 複用的Activity 或者 複用棧
        if (reusedActivity != null) {
        
            ... ... ...
			
            // 若是是 SINGLE_INSTANCE 或者 SINGLE_TASK 或者 含有 FLAG_ACTIVITY_CLEAR_TOP 標識
            //咱們能夠判斷出來 SINGLE_INSTANCE 或者 SINGLE_TASK 含有 FLAG_ACTIVITY_CLEAR_TOP 的效果
            if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                    || isDocumentLaunchesIntoExisting(mLaunchFlags)
                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {

                //拿 reuseActivity 的棧
                final TaskRecord task = reusedActivity.getTask();
			
		// 好比 singleTask 移除要啓動的Activity以前的全部Activity
                final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
                        mLaunchFlags);

                if (reusedActivity.getTask() == null) {
                    reusedActivity.setTask(task);
                }

                if (top != null) {
                    if (top.frontOfTask) {
                        // Activity aliases may mean we use different intents for the top activity,
                        // so make sure the task now has the identity of the new intent.
                        top.getTask().setIntent(mStartActivity);
                    }
	    	    //這裏是 SingleInstance 或者 SingleTask ,會執行onNewIntent
                    deliverNewIntent(top);
                }
            }
            
            ... ... ...
            
            //啓動者和被啓動者是同一個
            if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                // We don't need to start a new activity, and the client said not to do anything
                // if that is the case, so this is it! And for paranoia, make sure we have
                // correctly resumed the top activity.
                resumeTargetStackIfNeeded();
                return START_RETURN_INTENT_TO_CALLER;
            }
            
            if (reusedActivity != null) {
                //這裏會去判斷幾種狀況 singleTask singleInstance 和 singleTop
                setTaskFromIntentActivity(reusedActivity);

                if (!mAddingToTask && mReuseTask == null) {
                    
                    //singleInstance singleTask 都會走這裏
                    //1.好比要啓動的Activity是singleTask,且恰好在reusedActivity的棧內
                    //2.或者一個singleInstance模式的Activity再次被啓動
                    resumeTargetStackIfNeeded();
                    if (outActivity != null && outActivity.length > 0) {
                        outActivity[0] = reusedActivity;
                    }
                    return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
                }
            }
        }
複製代碼

繼續來看一下setTaskFromIntentActivity這個方法

private void setTaskFromIntentActivity(ActivityRecord intentActivity) {

        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
        
            //若是是 FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TASK
            final TaskRecord task = intentActivity.getTask();
            //清空task
            task.performClearTaskLocked();
            mReuseTask = task;
            mReuseTask.setIntent(mStartActivity);
        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {

            //若是是 singleInstance 或者 singleTask 走這裏,清空棧內要啓動的Activity以前的全部Activity們。
            ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
                    mLaunchFlags);
            //若是top == null 繼續走,不爲null,就結束了這個方法
            if (top == null) {
                ... ... ...
            }
        } else if (mStartActivity.realActivity.equals(intentActivity.getTask().realActivity)) {
            // 判斷是不是 singleTop 模式
            // 這種狀況如何復現? singleTop + FLAG_ACTIVITY_NEW_TASK + taskAffinity。
            // FLAG_ACTIVITY_NEW_TASK + taskAffinity去指定一個特定存在的棧,且棧頂是咱們要啓動的singleTop模式的activity
            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                        || LAUNCH_SINGLE_TOP == mLaunchMode)
                    && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
                if (intentActivity.frontOfTask) {
                    intentActivity.getTask().setIntent(mStartActivity);
                }
                deliverNewIntent(intentActivity);
            } else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
                ... ... ...
            }
        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
            ... ... ...
        } else if (!intentActivity.getTask().rootWasReset) {
            ... ... ...
        }
    }

複製代碼

2.4 小結一下

前提是reusedActivity不爲null,看兩種典型狀況:

1.若是是SingleTask或者是SingleInstance模式的Activity,則執行performClearTaskLocked方法,把要啓動的Activity以前的全部Activity都清除掉。

2.reusedActivity的啓動模式剛好是SingleTop,且也是咱們要啓動的Activity,執行 deliverNewIntent。

上述的兩種狀況可以知足下面的if判斷 !mAddingToTask && mReuseTask == null ,而後return結束。

思考一個問題,狀況1和2作例子來浮現一下?

2.5 判斷SINGLT_TOP模式

繼續看 startActivityUnchecked 後面的代碼,這一部分是針對SingleTop模式的處理。

注意哦,以前咱們也遇到了SingleTop模式的處理,就在上面的setTaskFromIntentActivity方法裏。這兩個有什麼區別呢?

區別在這裏的SingleTop模式判斷的棧是咱們當前展現的棧。而setTaskFromIntentActivity裏的判斷前提條件是在咱們的reusedActivity不爲空的狀況下,對reusedActivity進行的判斷,reusedActivity可能並非當前的棧。

... ... ...
        //下面這段英文解釋的很好(singleTop模式),當咱們要啓動的Actiivty剛好是當前棧頂的Activity,檢查是否只須要被啓動一次
        // 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.
        final ActivityStack topStack = mSupervisor.mFocusedStack;
        final ActivityRecord topFocused = topStack.getTopActivity();
        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
        final boolean dontStart = top != null && mStartActivity.resultTo == null
                && top.realActivity.equals(mStartActivity.realActivity)
                && top.userId == mStartActivity.userId
                && top.app != null && top.app.thread != null
                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));  
        // SINGLE_TOP SINGLE_TASK,要啓動的Activiy剛好在棧頂
        // dontStart爲true,表示不會去啓動新的Activity,複用棧頂的Activity
        if (dontStart) {
            // For paranoia, make sure we have correctly resumed the top activity.
            topStack.mLastPausedActivity = null;
            if (mDoResume) {
                // resume Activity
                mSupervisor.resumeFocusedStackTopActivityLocked();
            }
            ActivityOptions.abort(mOptions);
            if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                // We don't need to start a new activity, and the client said not to do
                // anything if that is the case, so this is it!
                return START_RETURN_INTENT_TO_CALLER;
            }

            deliverNewIntent(top);

            // Don't use mStartActivity.task to show the toast. We're not starting a new activity
            // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
            mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
                    preferredLaunchDisplayId, topStack);

            return START_DELIVERED_TO_TOP;
        }
複製代碼

2.5 小結一下

這部分的代碼主要處理SingleTop模式的Activity,要啓動的Activity是SingleTop模式,且也剛好在當前棧的頂部,執行deliverNewIntent。

思考一個小問題,啓動一個SingleTop模式的Activity,而後再次啓動一次它,它的生命週期如何變化呢?

答案是 onCreate -> onStart -> onResume -> onPause -> onNewIntent -> onResume。

2.6 棧的複用和新建

繼續分析startActivityUnChecked方法的最後一部分,這部分主要是關於棧是否須要新建。

... ... ...
    
        //必要條件是有FLAG_ACTIVITY_NEW_TASK
        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
            //要創建新棧或者使用已經存在的棧,FLAG_ACTIVITY_NEW_TASK是必要條件
            newTask = true;
            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
        } else if (mSourceRecord != null) {
            //把被啓動的mStartActivity放在啓動者mSourceRecord所在的棧上
            result = setTaskFromSourceRecord();
        }
     ... ... ...
     
複製代碼

setTaskFromReuseOrCreateNewTask 方法

private int setTaskFromReuseOrCreateNewTask( TaskRecord taskToAffiliate, ActivityStack topStack) {
            
        mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);

        if (mReuseTask == null) {
            //新建一個棧
            final TaskRecord task = mTargetStack.createTaskRecord(
                    mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
                    mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
                    mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
                    mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
                    mOptions);
            addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
            updateBounds(mStartActivity.getTask(), mLaunchParams.mBounds);

        } else {
            //用舊棧
            addOrReparentStartingActivity(mReuseTask, "setTaskFromReuseOrCreateNewTask");
        }
        ... ... ...
複製代碼

2.6 小結一下

咱們能夠看出,若是須要咱們去新建一個棧或者把咱們要啓動的Activity放在已經存在某個棧中,FLAG_ACTIVITY_NEW_TASK是必要條件。

三. 啓動模式流程圖

對於第二部分的描述我用一個圖來總結一下。

四. 總結

咱們先總結一下幾個重要的結論:

  1. FLAG_ACTIVITY_NEW_TASK是新建棧或者複用棧的必要條件。SingleTask,SingleInstance會爲mLaunchFlags自動添加FLAG_ACTIVITY_NEW_TASK。也就是說他們都有存在不使用當前棧的可能。SingleInstance是很好理解的,SingleTask須要注意下,關於SingleTask我會詳細說明,不用擔憂。

  2. 新建棧或者複用已經存在棧的充要條件是什麼?

(1) FLAG_ACTIVITY_NEW_TASK + taskAffinity(taskAffinity必須與當前顯示的棧的rootAffinity不相同,taskAffinity默認是包名)

(2) FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_MULTIPLE_TASK 這是一定會新建一個棧的

  1. SingleTask能夠理解成FLAG_ACTIVITY_TASK + CLEAR_TOP + (taskAffinity == 該應用包名)

  2. SingleInstance比較特殊,自己FLAG_ACTIVITY_NEW_TASK,特殊的地方其一在於若是啓動過了,會去遍歷找相等的Activity,查找過程不同。而不像SingleTask是去找合適存放的棧,根據taskAffinity來查找。其二在於SingleInstance一個棧只能存放一個Activity,能作到這個的緣由是咱們在根據taskAffinity找到合適的棧的時候,若是發現是SingleInstance模式Activity的棧,直接忽略。

回答問題

到這裏也算是完結了,咱們回過頭來思考一下最開始提出的幾個問題。

  • 若兩個應用A和B,A中有兩個Activity,A1和A2,B中有一個Activity B1,這三個Activity都是SingleTask模式,那麼啓動順序是A1 -> B1 -> A2,返回的其實是A1。由於SingleTask模式自己含有FLAG_ACTIVITY_NEW_TASK,這裏因爲taskAffinity也和當前展現的棧不相同,因此會去找"合適的"棧放入。

  • 接着前面問題,若是A2是Standard模式 + FlAG_ACTIVITY_NEW_TASK呢? 會回到A1,由於A2和A1在一個棧,相似第一種狀況。

  • FLAG_ACTIVITY_NEW_TASK 有什麼用?taskAffinity又是啥?這個問題能夠看上面的小結。

相關文章
相關標籤/搜索