Android 啓動模式--任務(Task)--桟 的誤區

Android 啓動模式--任務(Task)--桟 的誤區

寫這篇文章是由於前幾天的一次面試,面試官說SingleInstance模式會新建一個桟,而SingleTask不會.首先不說這個對不對(非要說對錯的話,那就是錯.),由於這句話是含糊不清的.?只的是返回桟? 仍是任務桟?有沒有考慮taskAffinity屬性?因此籠統的那樣說是不對的.這篇文章一是爲了記錄,二是爲了說清楚----任務(Task)& 桟(返回桟,任務桟).html

概念

桟(堆棧:stack)android

棧的基本特色:面試

  • 先入後出,後入先出。瀏覽器

  • 除頭尾節點以外,每一個元素有一個前驅,一個後繼。網絡

任務app

任務是指在執行特定做業時與用戶交互的一系列 Activity。 這些 Activity 按照各自的打開順序排列在堆棧(即返回棧)中。ide

返回桟
在官方文檔中,找不到關於返回桟的概念,可是按照官方文檔在描述時的語境,能夠理解爲響應Android返回鍵的順序隊列.這是站在用戶的直觀感覺上的描述.Android 內部的描述單位只有任務(Task).測試

任務桟
在官方文檔中沒有這個概念.我爲了方便理解,把任務中按照規則的順序隊列的 一系列 Activity 稱爲 任務棧.ui

我的理解

因爲 官方文檔中沒有詳細的說明什麼是返回桟,在實際的開發中若是以棧爲基本度量單位的話,很容易被本身繞暈,若是按照任務爲基本度量單位的話就很容易理清楚 Android 四大啓動模式了.我我的把返回棧理解爲這樣子的: 返回桟僅有一個(由於返回鍵只有一個),返回桟操做的基本元素是任務,返回鍵操做的是返回桟中處於前臺的任務,每一個任務維護着一系列 Activity 組成的以響應返回鍵.google

Activity 四大啓動模式

  • "standard"(默認模式)
    默認。系統在啓動 Activity 的任務中建立 Activity 的新實例並向其傳送 Intent。Activity 能夠屢次實例化,而每一個實例都可屬於不一樣的任務,而且一個任務能夠擁有多個實例。

  • "singleTop"
    若是當前任務的頂部已存在 Activity 的一個實例,則系統會經過調用該實例的 onNewIntent() 方法向其傳送 Intent,而不是建立 Activity 的新實例。Activity 能夠屢次實例化,而每一個實例都可屬於不一樣的任務,而且一個任務能夠擁有多個實例(但前提是位於返回棧頂部的 Activity 並非 Activity 的現有實例)。

例如,假設任務的返回棧包含根 Activity A 以及 Activity B、C 和位於頂部的 D(堆棧是 A-B-C-D;D 位於頂部)。收到針對 D 類 Activity 的 Intent。若是 D 具備默認的 "standard" 啓動模式,則會啓動該類的新實例,且堆棧會變成 A-B-C-D-D。可是,若是 D 的啓動模式是"singleTop",則 D 的現有實例會經過 onNewIntent() 接收 Intent,由於它位於堆棧的頂部;而堆棧仍爲 A-B-C-D。可是,若是收到針對 B 類 Activity 的 Intent,則會向堆棧添加 B 的新實例,即使其啓動模式爲 "singleTop" 也是如此。

注:爲某個 Activity 建立新實例時,用戶能夠按「返回」按鈕返回到前一個 Activity。 可是,當 Activity 的現有實例處理新 Intent 時,則在新 Intent 到達 onNewIntent() 以前,用戶沒法按「返回」按鈕返回到 Activity 的狀態。

  • "singleTask"
    系統建立新任務並實例化位於新任務底部的 Activity。可是,若是該 Activity 的一個實例已存在於一個單獨的任務中,則系統會經過調用現有實例的 onNewIntent() 方法向其傳送 Intent,而不是建立新實例。一次只能存在 Activity 的一個實例。

注:儘管 Activity 在新任務中啓動,可是用戶按「返回」按鈕仍會返回到前一個 Activity。

  • "singleInstance".
    與 "singleTask" 相同,只是系統不會將任何其餘 Activity 啓動到包含實例的任務中。該 Activity 始終是其任務惟一僅有的成員;由此 Activity 啓動的任何 Activity 均在單獨的任務中打開。

咱們再來看另外一示例,Android 瀏覽器應用聲明網絡瀏覽器 Activity 應始終在其本身的任務中打開(經過在 <activity> 元素中指定 singleTask 啓動模式)。這意味着,若是您的應用發出打開 Android 瀏覽器的 Intent,則其 Activity 與您的應用位於不一樣的任務中。相反,系統會爲瀏覽器啓動新任務,或者若是瀏覽器已有任務正在後臺運行,則會將該任務上移一層以處理新 Intent。

圖片描述

不管 Activity 是在新任務中啓動,仍是在與啓動 Activity 相同的任務中啓動,用戶按「返回」按鈕始終會轉到前一個 Activity。 可是,若是啓動指定 singleTask 啓動模式的 Activity,則當某後臺任務中存在該 Activity 的實例時,整個任務都會轉移到前臺。此時,返回棧包括上移到堆棧頂部的任務中的全部 Activity。 上圖顯示了這種狀況。

上圖 顯示如何將啓動模式爲「singleTask」的 Activity 添加到返回棧。 若是 Activity 已是某個擁有本身的返回棧的後臺任務的一部分,則整個返回棧也會上移到當前任務的頂部。

參考:2017-03-14日摘自官方文檔(後期可能有變動以官網爲準)

關於android:taskAffinity屬性

android:taskAffinity

與 Activity 有着親和關係的任務。從概念上講,具備相同親和關係的 Activity 歸屬同一任務(從用戶的角度來看,則是歸屬同一「應用」)。 任務的親和關係由其根 Activity 的親和關係肯定。
親和關係肯定兩件事 - Activity 更改到的父項任務(請參閱 allowTaskReparenting 屬性)和經過 FLAG_ACTIVITY_NEW_TASK 標誌啓動 Activity 時將用來容納它的任務。
默認狀況下,應用中的全部 Activity 都具備相同的親和關係。您能夠設置該屬性來以不一樣方式組合它們,甚至能夠將在不一樣應用中定義的 Activity 置於同一任務內。 要指定 Activity 與任何任務均無親和關係,請將其設置爲空字符串。
若是未設置該屬性,則 Activity 繼承爲應用設置的親和關係(請參閱 <application> 元素的 taskAffinity 屬性)。 應用默認親和關係的名稱是 <manifest> 元素設置的軟件包名稱。

參考:2017-03-14日摘自官方文檔(後期可能有變動以官網爲準)

測試 SingleTaskSingleInstance的異同

我寫了一個Demo測試了下:

當我把SingleTaskA配置爲:

<activity android:name=".SingleTaskA" android:launchMode="singleTask" android:taskAffinity="com.didikee.temp"/>

SingleTaskA中啓動一個標準模式的StandardBActivity:

public class SingleTaskA extends BaseLauncherModeActivity {
    @Override
    protected Class gotoActivity() {
        return StandardB.class;
    }

    @Override
    protected String setModeTextShow() {
        return Constant.SINGLE_TASK;
    }
}

而在StandardB中,我一直啓動StandardB本身:

public class StandardB extends BaseLauncherModeActivity {
    @Override
    protected Class gotoActivity() {
        return StandardB.class;
    }

    @Override
    protected String setModeTextShow() {
        return Constant.STANDARD+"Repeat";
    }
}

補充下基類:

public abstract class BaseLauncherModeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base_launcher_mode);
        String text = setModeTextShow();
        Button button = (Button) findViewById(R.id.bt);
        button.setText("模式: "+text);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Class aClass = gotoActivity();
                if (aClass==null)return;
                startActivity(new Intent(getApplicationContext(),aClass));
            }
        });
        int taskId = getTaskId();
        Log.d(Constant.TAG,"taskId: "+taskId);
    }

    protected abstract Class gotoActivity();

    protected abstract String setModeTextShow();
}

獲得以下結果:

03-14 10:32:32.287 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 26
03-14 10:32:37.691 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 26
03-14 10:32:42.120 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 27
03-14 10:32:45.961 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 27
03-14 10:32:52.105 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 27
03-14 10:32:53.531 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 27
03-14 10:32:55.748 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 27
03-14 10:32:56.763 22561-22561/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 27

能夠看到,SingleTaskA啓動了一個新的任務(Task),id爲27,以後在SingleTaskA中啓動StandardB並無作其餘的操做,而是直接在剛剛SingleTaskA啓動的任務中添加.

如今我去掉 taskAffinity屬性的定義,獲得:

03-14 16:42:10.604 31062-31062/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 29
03-14 16:42:15.261 31062-31062/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 29
03-14 16:42:16.865 31062-31062/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 29
03-14 16:42:19.197 31062-31062/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 29
03-14 16:42:20.232 31062-31062/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 29
03-14 16:42:21.049 31062-31062/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 29
03-14 16:42:22.180 31062-31062/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 29

可見,默認是不會建立新的任務的.

如今,把SingleTaskA 改成 SingleInstanceA,而後在其中能夠啓動StandardB(上面有代碼),獲得以下結果:

03-14 16:46:59.669 2273-2273/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 30
03-14 16:47:09.367 2273-2273/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 30
03-14 16:47:10.602 2273-2273/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 31
03-14 16:47:12.013 2273-2273/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 30
03-14 16:47:14.438 2273-2273/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 30
03-14 16:47:15.429 2273-2273/com.didikee.androidlaunchermode D/AndroidLauncherMode: taskId: 30

可見,SingleInstanceA新建了一個任務id爲31,在 SingleInstanceA 中啓動 StandardB 仍是繼續添加在以前任務中.

總結:

  1. Android 是以任務爲核心的,不要被棧帶偏了.

  2. SingleInstance 必定會新建一個任務,而且該任務中僅包含一個實例.

  3. SingleTask默認不會建立新任務,可是能夠經過taskAffinity達到建立新任務的目的,建立的任務可添加其餘元素.

最後,說的不對的歡迎交流.

相關文章
相關標籤/搜索