Android面試官裝逼失敗之:Activity的啟動模式

看書的小蝸牛發表於2017-09-07

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

  • Activity的taskAffinity屬性的Task棧是否存在
  • 如果存在,要看Activity是否存已經存在於該Task
  • 如果已經存在於該taskAffinity的Task,要看其是不是其rootActivity
  • 如果是其rootActivity,還要看啟動該Activity的Intent是否跟當前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
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_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

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

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
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
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
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_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的啟動模式
僅供參考,歡迎指正

相關文章