Android Activity的啓動模式分析

面試的時候,面試官經常同你隨便侃侃Activity的啓動模式,但Activity啓動牽扯的知識點其實很多,並非能單單用四個啓動模式就能概括的,默認的啓動模式的表現會隨着Intent Flag的設置而改變,因此侃Activity啓動模式大多走流程裝逼,最多結合項目遇到的問題,隨便刁難一下面試者,並不太容易把控,也許最後,面試官跟面試者的答案都是錯了,比如在Service中必須通過設置FLAG_ACTIVITY_NEW_TASK才能啓動Activity,這個時候啓動Activit會有什麼樣的表現呢?就這一個問題,答案就要分好幾個場景:

  • Activity的taskAffinity屬性的Task棧是否存在
  • 如果存在,要看Activity是否存已經在其taskAffinity的Task
  • 如果在其taskAffinity的Task,要看是不是其rootActivity
  • 如果是其rootActivity,要看啓動該Activity的Intent是否相等

不同場景,所表現的行爲都會有所不同,再比如singleInstance屬性,如果設置了,大家都知道只有一個實例,將來再啓動會複用,但是如果使用Intent.FLAG_ACTIVITY_CLEAR_TASK來啓動的時候,其實會重建,並非完全遵守singleInstance的說明,還有不同Flag在疊加使用時候也會有不同的表現,單一而論Activity啓動模式其實是很難的。本文也僅僅是涉及部分啓動模式及Flag,更多組合跟場景要自己看源碼或者實驗來解決了。

簡單基本launchmode

本文假定大家對於Activity的Task棧已經有初步瞭解,首先,看一下Activity常見的四種啓動模式及大衆理解,這也是面試時最長問的:

  • standard:標準啓動模式(默認啓動模式),每次都會啓動一個新的activity實例。
  • singleTop:單獨使用使用這種模式時,如果Activity實例位於當前任務棧頂,就重用棧頂實例,而不新建,並回調該實例onNewIntent()方法,否則走新建流程。
  • singleTask:這種模式啓動的Activity**只會存在相應的Activity的taskAffinit任務棧中**,同一時刻系統中只會存在一個實例,已存在的實例被再次啓動時,會重新喚起該實例,並清理當前Task任務棧該實例之上的所有Activity,同時回調onNewIntent()方法。
  • singleInstance:這種模式啓動的Activity獨自佔用一個Task任務棧,同一時刻系統中只會存在一個實例,已存在的實例被再次啓動時,只會喚起原實例,並回調onNewIntent()方法。

需要說明的是:上面的場景僅僅適用於Activity啓動Activity,並且採用的都是默認Intent,沒有額外添加任何Flag,否則表現就可能跟上面的完全不一致,尤其要注意的是FLAG_ACTIVITY_NEW_TASK的使用,後面從源碼中看,依靠FLAG_ACTIVITY_NEW_TASK其實可以分爲兩派。

Intent.FLAG_ACTIVITY_NEW_TASK分析

從源碼來看,Intent.FLAG_ACTIVITY_NEW_TASK是啓動模式中最關鍵的一個Flag,依據該Flag啓動模式可以分成兩類,設置了該屬性的與未設置該屬性的,對於非Activity啓動的Activity(比如Service或者通知中啓動的Activity)需要顯示的設置Intent.FLAG_ACTIVITY_NEW_TASK,而singleTask及singleInstance在AMS中被預處理後,隱形的設置了Intent.FLAG_ACTIVITY_NEW_TASK,而啓動模式是standard及singletTop的Activity不會被設置Intent.FLAG_ACTIVITY_NEW_TASK,除非通過顯示的intent setFlag進行設置。

FLAG_ACTIVITY_NEW_TASK這個屬性更多的關注點是在Task,大多數情況下,需要將Activity引入到自己taskAffinity的Task中,Intent.FLAG_ACTIVITY_NEW_TASK的初衷是在Activity目標taskAffinity的Task中啓動,非Activity啓動Activity都必須添加Intent.FLAG_ACTIVITY_NEW_TASK才行,以Service啓動的Activity爲例:

  Intent intent = new Intent(BackGroundService.this, A.class);
  intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  startActivity(intent);    

這種情況很有意思,如果目標Activity實例或者Task不存在,則一定會新建Activity,並將目標Task移動到前臺,但是如果Activity存在,卻並不一定複用,也不一定可見。這裏假定A是standard的Activity,如果已經有一個A實例,並且所在的堆棧的taskAffinity跟A的taskAffinity一致,這個時候要看這個task的根Activity是不是A,如果是A,還要看A的intent是不是跟當前的啓動的intent相等,如果都滿足,只要將task可見即可。否則,就需要新建A,並根據A的task棧的存在情況而選擇直接入棧還是新建棧。但是,如果Intent想要的啓動的Activity的目標堆棧存在,那就將整個堆棧往前遷移,如果位於頂部的Task棧正好是目標Activity的Task棧,那就不做任何處理,連onNewIntent都不會回調,怎麼判斷目標的Activity的Task棧同找到的棧一致呢?如果找不到目標Task自然會啓動Task,如果目標task棧根Activit的intent同新將要啓動的Activit相同,就不啓動新Activity,否則啓動Activity

Service 通過 FLAG_ACTIVITY_NEW_TASK.jpg

Intent.FLAG_ACTIVITY_CLEAR_TASK:必須配合FLAG_ACTIVITY_NEW_TASK使用

If set in an Intent passed to Context.startActivity(), this flag will cause any existing task that would be associated with the activity to be cleared before the activity is started. That is, the activity becomes the new root of an otherwise empty task, and any old activities are finished. This can only be used in conjunction with FLAG_ACTIVITY_NEW_TASK.

這個屬性必須同FLAG_ACTIVITY_NEW_TASK配合使用,如果設置了FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK,如果目標task已經存在,將清空已存在的目標Task,否則,新建一個Task棧,之後,新建一個Activity作爲根Activity。Intent.FLAG_ACTIVITY_CLEAR_TASK的優先級最高,基本可以無視所有的配置,包括啓動模式及Intent Flag,哪怕是singleInstance也會被finish,並重建。

Intent.FLAG_ACTIVITY_CLEAR_TASK.jpg

Intent.FLAG_ACTIVITY_CLEAR_TOP

如果沒有使用FLAG_ACTIVITY_NEW_TASK,目標是當前Task棧,根據不同的組合會產生不同的效果,如果單獨使用Intent.FLAG_ACTIVITY_CLEAR_TOP,並且沒有設置特殊的launchmode,那麼,Google官方的示例是:如果ABCD Task中的D採用Intent.FLAG_ACTIVITY_CLEAR_TOP喚起B,這個時候首先會將CD出棧,但是至於B是否會重建,要視情況而定,如果沒有設置FLAG_ACTIVITY_SINGLE_TOP,則會將B finish掉,之後創建新的入棧。如果同一個棧中原來有

Intent.FLAG_ACTIVITY_CLEAR_TOP同一個.jpg

如果沒有則新建,不會去另一個棧中尋找。

Intent.FLAG_ACTIVITY_CLEAR_TOP.jpg

如果同時設置了FLAG_ACTIVITY_SINGLE_TOP,在當前棧已有的情況下就不會重建,而是直接回調B的onNewIntent(),

Intent.FLAG_ACTIVITY_CLEAR_TOP|SINGLE_TOP.jpg

官方解釋如下:

For example, consider a task consisting of the activities: A, B, C, D. If D calls startActivity() with an Intent that resolves to the component of activity B, then C and D will be finished and B receive the given Intent, resulting in the stack now being: A, B。

The currently running instance of activity B in the above example will either receive the new intent you are starting here in its onNewIntent() method, or be itself finished and restarted with the new intent. If it has declared its launch mode to be “multiple” (the default) and you have not set FLAG_ACTIVITY_SINGLE_TOP in the same intent, then it will be finished and re-created; for all other launch modes or if FLAG_ACTIVITY_SINGLE_TOP is set then this Intent will be delivered to the current instance’s onNewIntent().

如果同時使用了FLAG_ACTIVITY_NEW_TASK ,這個時候,目標是Activity自己所屬的Task棧,如果在自己的Task中能找到一個Activity實例,則將其上面的及自身清理掉,之後重建。

Intent.FLAG_ACTIVITY_CLEAR_TOP| FLAG_ACTIVITY_NEW_TASK.jpg

如果同時在加上FLAG_ACTIVITY_SINGLE_TOP,會更特殊一些,如果topActivity不是目標Activity,就會去目標Task中去找,並喚起

Intent.FLAG_ACTIVITY_CLEAR_TOP| FLAG_ACTIVITY_NEW_TASK|singleTop.jpg

如果topActivity是目標Activity,就直接回調topActivity的onNewIntent,無論topActivity是不是在目標Task中

Intent.FLAG_ACTIVITY_CLEAR_TOP| FLAG_ACTIVITY_NEW_TASK|singleTop|top.jpg

Intent.FLAG_ACTIVITY_SINGLE_TOP

Intent.FLAG_ACTIVITY_SINGLE_TOP多用來做輔助作用,跟launchmode中的singleTop作用一樣,在Task棧頂有的話,就不新建,棧頂沒有的話,就新建,這裏的Task可能是目標棧,也可能是當前Task棧,配合FLAG_ACTIVITY_NEW_TASK及FLAG_ACTIVITY_CLEAR_TOP都會有很有意思的效果。

源碼分析

現在我們看一下源碼,來分析下原理:從源碼更能看出FLAG_ACTIVITY_NEW_TASK重要性,這裏只分析部分場景,不然太多了: 下面的是4.3的代碼,比較簡單些:

final int startActivityUncheckedLocked(ActivityRecord r,
        ActivityRecord sourceRecord, int startFlags, boolean doResume,
        Bundle options) {
    final Intent intent = r.intent;
    final int callingUid = r.launchedFromUid;
    ...
    <!--關鍵點1:直接獲取launchFlags--> 
    int launchFlags = intent.getFlags();
    ...
     <!--關鍵點2:預處理一些特殊的 launchmode,主要是設置Intent.FLAG_ACTIVITY_NEW_TASK-->
    // 如果sourceRecord ==null 說明不是從activity啓動的,從服務開啓就一定要start a new task
    if (sourceRecord == null) {
        if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
        }
    } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
        launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
    } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
        launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
    }
    ...
    <!--關鍵點3 對於Intent.FLAG_ACTIVITY_NEW_TASK 對sourceActivity直接返回cancel-->
    if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
        sendActivityResultLocked(-1,
                r.resultTo, r.resultWho, r.requestCode,
            Activity.RESULT_CANCELED, null);
        r.resultTo = null;
    }
    boolean addingToTask = false;
    boolean movedHome = false;
    TaskRecord reuseTask = null;
     <!--關鍵點4 對於Intent.FLAG_ACTIVITY_NEW_TASK分支的處理-->
    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.resultTo == null) {
            <!--關鍵點5找到目標Task棧的棧頂元素 ,但是taskTop不一定是目標Activity-->   
            ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
                    ?findTaskLocked(intent, r.info)
                    : findActivityLocked(intent, r.info);
            <!--如果目標棧存在-->
            if (taskTop != null) {
                ActivityRecord curTop = topRunningNonDelayedActivityLocked(notTop);
                <!--關鍵點6 看找到的Task是不是位於棧頂,如果不是則移動-->
                if (curTop != null && curTop.task != taskTop.task) {
                    r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
                    boolean callerAtFront = sourceRecord == null
                            || curTop.task == sourceRecord.task;
                    if (callerAtFront) {
                        movedHome = true;
                        moveHomeToFrontFromLaunchLocked(launchFlags);
                        moveTaskToFrontLocked(taskTop.task, r, options);
                        options = null;
                    }
                }
                    ...
                    <!--關鍵點6 如果設置了Intent.FLAG_ACTIVITY_CLEAR_TASK,則重置Task,併爲reuseTask賦值,這個屬性只能配合Intent.FLAG_ACTIVITY_NEW_TASK使用-->
                if ((launchFlags &
                        (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
                        == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
                    reuseTask = taskTop.task;
                    performClearTaskLocked(taskTop.task.taskId);
                    reuseTask.setIntent(r.intent, r.info);
                 <!--關鍵點7 如果設置了Intent.FLAG_ACTIVITY_CLEAR_TOP,則將Task中目標Activity之上的清空,至於自己是否要清空,還要看是不是設置了singltTop-->
                } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
                        || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
                        || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                    <!--如果能找到一個SingleTop,則不用重建-->
                    ActivityRecord top = performClearTaskLocked(
                            taskTop.task.taskId, r, launchFlags);
                    if (top != null) {
                    <!--如果自己是rootActivity,則重置intent-->
                        if (top.frontOfTask) {
                            top.task.setIntent(r.intent, r.info);
                        }
                        top.deliverNewIntentLocked(callingUid, r.intent);
                    } else {
                     <!--找不到的話,就新建 ,並且爲sourceRecord賦值-->
                        addingToTask = true;
                        sourceRecord = taskTop;
                    }
                } else if (r.realActivity.equals(taskTop.task.realActivity)) {
                        <!--如果taskTop的Task rootActivity是目標Activity,則額外,如果是Intent.FLAG_ACTIVITY_SINGLE_TOP,並自己位於棧頂 不新建-->
                    if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP)
                            && taskTop.realActivity.equals(r.realActivity)) {
                        if (taskTop.frontOfTask) {
                            taskTop.task.setIntent(r.intent, r.info);
                        }
                        taskTop.deliverNewIntentLocked(callingUid, r.intent);
                        <!--如果root intent不相等,則新建Activity,並加入目標棧-->
                    } else if (!r.intent.filterEquals(taskTop.task.intent)) {
                        addingToTask = true;
                        sourceRecord = taskTop;
                    }
                }
                 ...
                 <!--以上部分主要是對Intent.FLAG_ACTIVITY_NEW_TASK的處理,有些需要直接返回的場景-->
                if (!addingToTask && reuseTask == null) {
                    if (doResume) {
                        resumeTopActivityLocked(null, options);
                    } else {
                        ActivityOptions.abort(options);
                    }
                    return ActivityManager.START_TASK_TO_FRONT;
                }
            }
        }
    }

    if (r.packageName != null) {
        ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
        if (top != null && r.resultTo == null) {
            if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
                if (top.app != null && top.app.thread != null) {
               <!--可能不是目標task,但是是Intent.FLAG_ACTIVITY_SINGLE_TOP-->
                    if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
                        || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
                        || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
                        logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
                        if (doResume) {
                            resumeTopActivityLocked(null);
                        }
                        ActivityOptions.abort(options);
                        if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
                            return ActivityManager.START_RETURN_INTENT_TO_CALLER;
                        }
                        top.deliverNewIntentLocked(callingUid, r.intent);
                        return ActivityManager.START_DELIVERED_TO_TOP;
                    }
                }
            }
        }

    }
    ...

    boolean newTask = false;
    boolean keepCurTransition = false;

    <!--最終判斷是不是要啓動新Task棧-->
    if (r.resultTo == null && !addingToTask
            && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
        <!--新建Task 沒找到reuseTask task 並且是launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK-->
        if (reuseTask == null) {
            mService.mCurTask++;
            if (mService.mCurTask <= 0) {
                mService.mCurTask = 1;
            }
            r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true);
        } else {
        <!--找到Task,但是rootActivity不是目標Activiyt,則需要新建activity-->
            r.setTask(reuseTask, reuseTask, true);
        }
        newTask = true;
        if (!movedHome) {
            moveHomeToFrontFromLaunchLocked(launchFlags);
        }

    } else if (sourceRecord != null) {
            <!--非服務類啓動的Activity,如果當前棧能找到目標棧的singleTop,則不新建,否則新建-->
        if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
            ActivityRecord top = performClearTaskLocked(
                    sourceRecord.task.taskId, r, launchFlags);
            keepCurTransition = true;
            if (top != null) {
                top.deliverNewIntentLocked(callingUid, r.intent);
                if (doResume) {
                    resumeTopActivityLocked(null);
                }
                ActivityOptions.abort(options);
                return ActivityManager.START_DELIVERED_TO_TOP;
            }
        } 
        ...
        r.setTask(sourceRecord.task, sourceRecord.thumbHolder, false);
    } else {
        final int N = mHistory.size();
        ActivityRecord prev =
            N > 0 ? mHistory.get(N-1) : null;
        r.setTask(prev != null
                ? prev.task
                : new TaskRecord(mService.mCurTask, r.info, intent), null, true);
   }
    startActivityLocked(r, newTask, doResume, keepCurTransition, options);
    return ActivityManager.START_SUCCESS;
}

爲什麼非Activity啓動Activity要強制規定使用參數FLAG_ACTIVITY_NEW_TASK

從源碼上說,ContextImpl在前期做了檢查,如果沒添加Intent.FLAG_ACTIVITY_NEW_TASK就拋出異常,

@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();
    if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
    }
    ...
}

爲什麼要這麼呢?其實直觀很好理解,如果不是在Activity中啓動的,那就可以看做不是用戶主動的行爲,也就說這個界面可能出現在任何APP之上,如果不用Intent.FLAG_ACTIVITY_NEW_TASK將其限制在自己的Task中,那用戶可能會認爲該Activity是當前可見APP的頁面,這是不合理的。舉個例子:我們在聽音樂,這個時候如果郵件Service突然要打開一個Activity,如果不用Intent.FLAG_ACTIVITY_NEW_TASK做限制,那用戶可能認爲這個Activity是屬於音樂APP的,因爲用戶點擊返回的時候,可能會回到音樂,而不是郵件(如果郵件之前就有界面)。

總結

以上分析只是針對一個版本的Android,並且只涉及部分Flag,要完全理解各種組合就更麻煩了,所以所,如果面試官問題Activity啓動模式的話,隨便侃侃還可以,但是要以此來鄙視你,那你有90%的概率可以懟回去,怎麼懟?隨便幾個Flag組合一下,問面試官會有什麼結果,保證問死一堆 ^_^;

讓你裝逼

作者:看書的小蝸牛
原文鏈接: Android面試官裝逼失敗之:Activity的啓動模式
僅供參考,歡迎指正

發佈了51 篇原創文章 · 獲贊 33 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章