1. 程式人生 > >activity的啟動模式有哪些?是什麼含義?(二)

activity的啟動模式有哪些?是什麼含義?(二)

原始碼分析相關面試題

Activity相關面試題

與XMPP相關面試題

與效能優化相關面試題

與登入相關面試題

與開發相關面試題

與人事相關面試題

例項驗證singleTask啟動模式

上篇文章將activity的四種啟動模式就基本介紹完了。為了加深對啟動模式的瞭解,下面會通過一個簡單的例子進行驗證。由以上的介紹可知,standard和singleTop這兩種啟動模式行為比較簡單,所以在下面的例子中,通過taskAffinity會對singleTask和singleInstance著重介紹。

驗證啟動singleTask模式的activity時是否會建立新的任務

這個例項中有三個Activity,分別為:MainActivity,SecondActivity和ThirdActivity。

配置形式:

<activity  android:label="@string/app_name"
                   android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category
android:name="android.intent.category.LAUNCHER" />
</intent-filter> </activity> <activity android:name=".SecondActivity" android:launchMode="singleTask"> <intent-filter > <action android:name="com.maweiqi.SecondActivity"
/>
<category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".ThirdActivity" android:label="@string/app_name" > </activity>

說明:

MainActivity和ThirdActivity都是標準的啟動模式,而SecondActivity的啟動模式為singleTask。

使用案例:

public class MainActivity extends AppCompatActivity {
    private static final String ACTIVITY_NAME = "MainActivity";
    private static final String LOG_TAG = "maweiqi";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);

                startActivity(intent);
            }
        });

        int taskId = getTaskId();
        Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + 
                " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
    }
}
public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
                startActivity(intent);
            }
        });

        Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());

    }
}
public class ThirdActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
    }
}

測試方式:

MainActivity –>SecondActivity –>ThirdActivity。

輸出的日誌如下:

onCreateMainActivity TaskId: 21 hasCode:199785693
onCreateSecondActivity TaskId: 21 hasCode:171956091

在命令列中執行以下命令 adb shell dumpsys activity ,有以下輸出:

Running activities (most recent first):
      TaskRecord{838c0b8 #21 A=com.open.android.task1 U=0 sz=2}
        Run #1: ActivityRecord{abf1b0a u0 com.open.android.task1/.SecondActivity t21}
        Run #0: ActivityRecord{4e579b u0 com.open.android.task1/.MainActivity t21}

測試結果:

1)MainActivity和SecondActivity是啟動在同一個任務中

2)在SecondActivity增加一個taskAffinity屬性,如下所示:

<activity android:name=".SecondActivity"
                  android:launchMode="singleTask"
                  android:taskAffinity="com.maweiqi.second">
            <intent-filter >
                <action android:name="com.maweiqi.SecondActivity"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

測試方式:

MainActivity –>SecondActivity –>ThirdActivity。

輸出的日誌如下:

onCreateMainActivity TaskId: 24 hasCode:199785693
onCreateSecondActivity TaskId: 25 hasCode:171956091
onCreateThirdActivity TaskId: 25 hasCode:76684615

在命令列中執行以下命令 adb shell dumpsys activity ,有以下輸出:

Running activities (most recent first):
      TaskRecord{844539b #25 A=com.maweiqi.second U=0 sz=2}
        Run #2: ActivityRecord{2eb5348 u0 com.open.android.task1/.ThirdActivity t25}
        Run #1: ActivityRecord{119f0df u0 com.open.android.task1/.SecondActivity t25}
      TaskRecord{b730338 #24 A=com.open.android.task1 U=0 sz=1}
        Run #0: ActivityRecord{2e05e2a u0 com.open.android.task1/.MainActivity t24}

測試結果:

1)MainActivity和SecondActivity執行在不同的任務中了

2)ThirdActivity和SecondActivity執行在同一個任務中

在這裡便引出了manifest檔案中的一個重要屬性,taskAffinity。在官方文件中可以得到關於taskAffinity的以下資訊

taskAffinity

1)taskAffinity表示當前activity具有親和力的一個任務(翻譯不是很準確,原句為The task that the activity has an affinity for.),大致可以這樣理解,這個 taskAffinity表示一個任務,這個任務就是當前activity所在的任務。

2)在概念上,具有相同的affinity的activity(即設定了相同taskAffinity屬性的activity)屬於同一個任務。

3) 一個任務的affinity決定於這個任務的根activity(root activity)的taskAffinity。

4) 預設情況下,一個應用中的所有activity具有相同的taskAffinity,即應用程式的包名。我們可以通過設定不同的taskAffinity屬性給應用中的activity分組,也可以把不同的 應用中的activity的taskAffinity設定成相同的值。

5)為一個activity的taskAffinity設定一個空字串,表明這個activity不屬於任何task。

結果分析:

1)這就可以解釋上面示例中的現象了,由第4條可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity為com.open.android.task1,SecondActivity的taskAffinity為com.maweiqi.second。

2)當新啟動的activity(SecondActivity)是以FLAG_ACTIVITY_NEW_TASK標誌啟動時(只有singleTask模式才會保證“single in task”,只使用FLAG_ACTIVITY_NEW_TASK並不保證“single in task”,或者說singleTask包含了FLAG_ACTIVITY_NEW_TASK,反之卻不成立),framework會檢索是否已經存在了一個affinity為com.maweiqi.second的任務(即一個TaskRecord物件)

3)如果存在這樣的一個任務,則檢查在這個任務中是否已經有了一個SecondActivity的例項。

3-1)如果已經存在一個SecondActivity的例項,則會重用這個任務和任務中的SecondActivity例項,將這個任務調到前臺,清除位於SecondActivity上面的所有Activity,顯示SecondActivity,並呼叫SecondActivity的onNewIntent();

3-2)如果不存在一個SecondActivity的例項,會在這個任務中建立SecondActivity的例項,並呼叫onCreate()方法

3-3)這是在設定了singleTask模式的情況下會這樣,在沒有設定singleTask模式的情況下(即預設的standard模式)使用FLAG_ACTIVITY_NEW_TASK,並不會清除位於SecondActivity上面的所有Activity,而是會在task的上面重新建立一個SecondActivity。也就是說此時task中有兩個SecondActivity。

4)如果不存在這樣的一個任務,會建立一個新的affinity為com.maweiqi.second的任務,並且將SecondActivity啟動到這個新的任務中

上面討論的是設定taskAffinity屬性的情況,如果SecondActivity只設置啟動模式為singleTask,而不設定taskAffinity,即三個Activity的taskAffinity相同,都為應用的包名,那麼SecondActivity是不會開啟一個新任務的,framework中的判定過程如下:

1)在MainActivity啟動SecondActivity時,發現啟動模式為singleTask,那麼設定他的啟動標誌為FLAG_ACTIVITY_NEW_TASK

2)然後獲得SecondActivity的taskAffinity,即為包名com.open.android.task1檢查是否已經存在一個affinity為com.open.android.task1的任務,這個任務是存在的,就是MainActivity所在的任務,這個任務是在啟動MainActivity時開啟的

3)既然已經存在這個任務,就檢索在這個任務中是否存在一個SecondActivity的例項,發現不存在在這個已有的任務中啟動一個SecondActivity的例項

為了作一個清楚的比較,列出SecondActivity啟動模式設為singleTask,並且taskAffinity設為com.maweiqi.second時的啟動過程

1)在MainActivity啟動SecondActivity時,發現啟動模式為singleTask,那麼設定他的啟動標誌為FLAG_ACTIVITY_NEW_TASK

2)然後獲得SecondActivity的taskAffinity,即com.maweiqi.second檢查是否已經存在一個affinity為com.maweiqi.second的任務,這個任務是不存在的建立一個新的affinity為com.maweiqi.second的任務,並且將SecondActivity啟動到這個新的任務中

framework中對任務和activity的排程是很複雜的,尤其是把啟動模式設為singleTask或者以FLAG_ACTIVITY_NEW_TASK標誌啟動時.所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK時,要仔細測試應用程式.

例項驗證將兩個不同app中的不同的singleTask模式的Activity的taskAffinity設成相同

官方文件中提到,可以把不同的 應用中的activity的taskAffinity設定成相同的值,這樣的話這兩個activity雖然不在同一應用中,卻會在執行時分配到同一任務中,下面對此進行驗證,在這裡,會使用上面的示例,並建立一個新的示例AndroidTaskTest3。AndroidTaskTest3由兩個activity組成,分別為MianActivity和OtherActivity,在MianActivity中點選按鈕會啟動OtherActivity,兩個應用程式碼和佈局一樣,僅列出清單檔案,兩份清單檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.open.android.task1">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>



        <activity android:name=".SecondActivity"
                  android:launchMode="singleTask"
                  android:taskAffinity="com.maweiqi.second">
            <intent-filter >
                <action android:name="com.maweiqi.SecondActivity"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

        <activity android:name=".ThirdActivity"/>

    </application>

</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.open.android.task3">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name=".OtherActivity"
            android:taskAffinity="com.maweiqi.second"
            android:launchMode="singleTask">
        </activity>
    </application>

</manifest>

可以看到OtherActivity和SecondActivity的啟動模式都被設定為singleTask,並且taskAffinity屬性被設定為com.maweiqi.second.現在將這兩個應用安裝在裝置上。執行以下操作:

測試方式:

1)MainActivity –> SecondActivity

2)MainActivity –> OtherActivity

啟動AndroidTaskTest應用,在它的MianActivity中點選按鈕開啟SecondActivity,由上面的介紹可知secondActivity是執行在一個新任務中的,這個任務就是com.maweiqi.second。

然後按Home鍵回到Launcher,啟動AndroidTaskTest3,在啟動AndroidTaskTest3的入口MianActivity時,會自動啟動新的任務,那麼現在一共有三個任務,AndroidTaskTest的MianActivity和SecondActivity分別佔用一個任務,AndroidTaskTest3的MianActivity也佔用一個任務。

在AndroidTaskTest3的MianActivity中點選按鈕啟動OtherActivity,那麼這個OtherActivity是在哪個任務中呢?

日誌輸出

***AndroidTaskTest應用測試日誌輸出***
onCreateMainActivity TaskId: 29 hasCode:193251508
onCreateSecondActivity TaskId: 30 hasCode:171956091

***AndroidTaskTest3應用測試日誌輸出***
onCreateMainActivity TaskId: 31 hasCode:199785693
onCreateOtherActivity TaskId: 30 hasCode:171956091

下面執行adb shell dumpsys activity命令,發現有以下輸出:

  ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
  Stack #9:
    Task id #30
      TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}
      Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity }
        Hist #1: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}
          Intent { flg=0x10400000 cmp=com.open.android.task3/.OtherActivity }
          ProcessRecord{12090b5 27543:com.open.android.task3/u0a62}
        Hist #0: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30}
          Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity }
          ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59}
    Task id #31
      TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity }
        Hist #0: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity }
          ProcessRecord{12090b5 27543:com.open.android.task3/u0a62}
    Task id #29
      TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity }
        Hist #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity }
          ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59}

    Running activities (most recent first):
      TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}
        Run #3: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}
      TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1}
        Run #2: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31}
      TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2}
        Run #1: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30}
      TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1}
        Run #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29}

    mResumedActivity: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30}

結果分析

1)所以由此可見,AndroidTaskTest1的SecondActivity和AndroidTaskTest3的OtherActivity是在同一任務中的

2)由上面adb shell dumpsys activity命令的輸出結果還可以看出,AndroidTaskTest1和AndroidTaskTest3這兩個應用程式會開啟兩個程序,他們的所有元件分別執行在獨立的程序中

3)AndroidTaskTest1所在程序的程序號為u0a59,AndroidTaskTest3所在程序的程序號為u0a62

4)com.maweiqi.second任務中的兩個activity屬於不同的應用,並且執行在不同的程序中,這也說明了一個問題:任務(Task)不僅可以跨應用(Application),還可以跨程序(Process)。

例項驗證singleTask的另一意義:在同一個任務中具有唯一性

singleTask模式只意味著“可以在一個新的任務中開啟”,至於是不是真的會在新任務中開啟,在framework中還有其他條件的限制。由上面的介紹可知,這個條件為:是否已經存在了一個由他的taskAffinity屬性指定的任務。這一點具有迷惑性,我們在看到singleTask這個單詞的時候,會直觀的想到它的本意:single in task。即,在同一個任務中,只會有一個該activity的例項。現在讓我們進行驗證:

為了驗證這種情況,需要修改一下上面用到的AndroidTaskTest1示例。增加一個FourthActivity,並且MianActivity,SecondActivity,ThirdActivity和FourthActivity這四個activity都不設定taskAffinity屬性,並且將SecondActivity啟動模式設為singleTask,這樣這四個activity會在同一個任務中開啟。程式碼和軟體介面就不列出了,只列出清單檔案。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.open.android.task1">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>



        <activity android:name=".SecondActivity"
                  android:launchMode="singleTask"
                  >

        </activity>

        <activity android:name=".ThirdActivity"/>
        <activity android:name=".FourthActivity"/>
    </application>

</manifest>

測試方式:

MianActivity –> SecondActivity –> ThirdActivity –> FourthActivity

日誌輸出

onCreateMainActivity TaskId: 34 hasCode:199785693
onCreateSecondActivity TaskId: 34 hasCode:171956091
onCreateThirdActivity TaskId: 34 hasCode:240438941
onCreateFourthActivity TaskId: 34 hasCode:168147209

由此可見這四個activity都是在同一個任務中的。再次執行adb shell dumpsys activity命令加以驗證:

    Running activities (most recent first):
      TaskRecord{d114530 #34 A=com.open.android.task1 U=0 sz=4}
        Run #3: ActivityRecord{edae6a3 u0 com.open.android.task1/.FourthActivity t34}
        Run #2: ActivityRecord{f9339a5 u0 com.open.android.task1/.ThirdActivity t34}
        Run #1: ActivityRecord{1a30096 u0 com.open.android.task1/.SecondActivity t34}
        Run #0: ActivityRecord{7e96fa4 u0 com.open.android.task1/.MainActivity t34}

測試結果:

1)同樣可以說明目前這四個activity都執行在affinity為com.open.android.task1的任務中,即棧中的狀態為MainActivity –> SecondActivity –> ThirdActivity –> FourthActivity。

下面執行在FourthActivity中點選按鈕啟動SecondActivity的操作,注意,SecondActivity的啟動模式為singleTask,那麼現在棧中的情況如何呢?

日誌輸出

沒有日誌輸出,說明沒有呼叫onCreate方法

再次執行adb shell dumpsys activity命令,有以下輸出:

 Running activities (most recent first):
      TaskRecord{d114530 #34 A=com.open.android.task1 U=0 sz=2}
        Run #1: ActivityRecord{1a30096 u0 com.open.android.task1/.SecondActivity t34}
        Run #0: ActivityRecord{7e96fa4 u0 com.open.android.task1/.MainActivity t34}

測試結果:

1)這時棧中的狀態為MainActivity –> SecondActivity。確實確保了在任務中是唯一的,並且清除了同一任務中它上面的所有Activity。

2)那麼這個SecondActivity的例項是重用的上次已有的例項還是重新啟動了一個例項呢?可以觀察系統Log, 發現系統Log沒有改變,還是上面的四條Log。列印Log的語句是在各個Activity中的onCreate方法中執行的,沒有打印出新的Log,說明SecondActivity的onCreate的方法沒有重新執行,也就是說是重用的上次已經啟動的例項,而不是銷燬重建。

結果分析:

1)經過上面的驗證,可以得出如下的結論:在啟動一個singleTask的Activity例項時,如果系統中已經存在這樣一個例項,就會將這個例項排程到任務棧的棧頂,並清除它當前所在任務中位於它上面的所有的activity。

例項驗證singleInstance的行為

考谷歌官方文件,singleInstance的特點可以歸結為以下三條:

1)以singleInstance模式啟動的Activity具有全域性唯一性,即整個系統中只會存在一個這樣的例項

2)以singleInstance模式啟動的Activity具有獨佔性,即它會獨自佔用一個任務,被他開啟的任何activity都會執行在其他任務中(官方文件上的描述為,singleInstance模式的Activity不允許其他Activity和它共存在一個任務中)

3)被singleInstance模式的Activity開啟的其他activity,能夠開啟一個新任務,但不一定開啟新的任務,也可能在已有的一個任務中開啟

下面對這三個特點分別驗證,所使用的示例同樣為AndroidTaskTest1,只不過會進行一些修改,下面列出它的清單檔案:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.open.android.task1">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>



        <activity android:name=".SecondActivity"
                  android:launchMode="singleInstance"
                  >
            <intent-filter>
                <action android:name="com.maweiqi.ACTION_MY"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

        <activity android:name=".ThirdActivity"/>
        <activity android:name=".FourthActivity"/>
    </application>

</manifest>

由上面的清單檔案可以知道,該應用包括四個activity,分別為MianActivity,SecondActivity,ThirdActivity,FourthActivity,其中SecondActivity啟動模式設定為singleInstance。MianActivity可以開啟SecondActivity,SecondActivity可以開啟ThirdActivity。 並且為了可以在其他應用中開啟SecondActivity,為SecondActivity設定了一個IntentFilter,這樣就可以在其他應用中使用隱式Intent開啟SecondActivity。

測試方式:

MianActivity –> SecondActivity –> ThirdActivity

為了更好的驗證singleInstance的全域性唯一性,還需要其他一個應用,新建AndroidTaskTest4。AndroidTaskTest4只需要一個MianActivity,在MainActivity中點選按鈕會開啟AndroidTaskTest1應用中的SecondActivity。開啟AndroidTaskTest1應用中的SecondActivity的程式碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setAction("com.maweiqi.ACTION_MY");
                startActivity(intent);
            }
        });
    }
}

下面開始驗證第一個特點:以singleInstance模式啟動的Activity具有全域性唯一性,即整個系統中只會存在一個這樣的例項

執行如下操作:安裝AndroidTaskTest1應用,點選MainActivity中的按鈕,開啟SecondActivity,可以看到如下log輸出:

日誌輸出

onCreateMainActivity TaskId: 35 hasCode:199785693
onCreateSecondActivity TaskId: 36 hasCode:171956091

執行adb shell dumpsys activity命令,有以下輸出:

  Running activities (most recent first):
      TaskRecord{2b68544 #36 A=com.open.android.task1 U=0 sz=1}
        Run #1: ActivityRecord{6d9f8c u0 com.open.android.task1/.SecondActivity t36}
      TaskRecord{ae9a62d #35 A=com.open.android.task1 U=0 sz=1}
        Run #0: ActivityRecord{d0429f5 u0 com.open.android.task1/.MainActivity t35}

以上可以說明,singleInstance模式的Activity總是會在新的任務中執行(前提是系統中還不存在這樣的一個例項) 。

下面驗證它的全域性唯一性,執行以下操作:安裝另一個應用AndroidTaskTest4,在開啟的MainActivity中點選按鈕開啟AndroidTaskTest1應用中的SecondActivity。看到打印出一條新的日誌:

{act=com.maweiqi.ACTION_MY cmp=com.open.android.task1/.SecondActivity} from uid 10063 on display 0

執行adb shell dumpsys activity命令,有以下輸出:

      Running activities (most recent first):
      TaskRecord{2b68544 #36 A=com.open.android.task1 U=0 sz=1}
        Run #2: ActivityRecord{6d9f8c u0 com.open.android.task1/.SecondActivity t36}
      TaskRecord{a7e9abd #37 A=com.open.android.task4 U=0 sz=1}
        Run #1: ActivityRecord{f50eec0 u0 com.open.android.task4/.MainActivity t37}
      TaskRecord{ae9a62d #35 A=com.open.android.task1 U=0 sz=1}
        Run #0: ActivityRecord{d0429f5 u0 com.open.android.task1/.MainActivity t35}

測試結果:

1)開啟的SecondActivity就是上次建立的編號為6d9f8c的SecondActivity。

2)Log中沒有再次輸出關於SecondActivity的資訊,說明SecondActivity並沒有重新建立

結果分析:

以singleInstance模式啟動的Activity在整個系統中是單例的,如果在啟動這樣的Activiyt時,已經存在了一個例項,那麼會把它所在的任務排程到前臺,重用這個例項。

下面開始驗證第二個特點:以singleInstance模式啟動的Activity具有獨佔性,即它會獨自佔用一個任務,被他開啟的任何activity都會執行在其他任務中

重新安裝AndroidTaskTest1應用,點選MainActivity中的按鈕,開啟SecondActivity,在SecondActivity中點選按鈕,開啟ThirdActivity。可以看到有如下Log輸出:

測試方式:

MainActivity –> SecondActivity –> ThirdActivity

日誌輸出:

onCreateMainActivity TaskId: 42 hasCode:199785693
onCreateSecondActivity TaskId: 43 hasCode:171956091
onCreateThirdActivity TaskId: 42 hasCode:76684615

執行adb shell dumpsys activity命令,有以下輸出:

 Running activities (most recent first):
      TaskRecord{ace0710 #42 A=com.open.android.task1 U=0 sz=2}
        Run #3: ActivityRecord{322319f u0 com.open.android.task1/.ThirdActivity t42}
      TaskRecord{abc9c0e #43 A=com.open.android.task1 U=0 sz=1}
        Run #2: ActivityRecord{903a842 u0 com.open.android.task1/.SecondActivity t43}
      TaskRecord{ace0710 #42 A=com.open.android.task1 U=0 sz=2}
        Run #1: ActivityRecord{e3c0ddf u0 com.open.android.task1/.MainActivity t42}

測試結果:

SecondActivity所在的任務為43,被SecondActivity啟動的ThirdActivity所在的任務為42,這就說明以singleInstance模式啟動的Activity具有獨佔性,即它會獨自佔用一個任務,被他開啟的任何activity都會執行在其他任務中

下面開始驗證第三個特點:被singleInstance模式的Activity開啟的其他activity,能夠在新的任務中啟動,但不一定開啟新的任務,也可能在已有的一個任務中開啟

有上面對第二個特點的驗證可以看到,被SecondActivity啟動的ThirdActivity並沒有執行在一個新開啟的任務中,而是和MainActivity執行在了同一個已有的任務中,那麼在什麼情況下ThirdActivity才會啟動一個新的任務呢?

現在對程式的清單檔案做以下修改,為ThirdActivity增加一個屬性taskAffinity:

配置如下:

<activity android:name=".ThirdActivity"
                  android:taskAffinity="com.maweiqi.second"/>

重新安裝AndroidTaskTest1應用,執行和上一步中同樣的操作:點選MainActivity中的按鈕,開啟SecondActivity,在SecondActivity中點選按鈕,開啟ThirdActivity。可以看到有如下輸出:

onCreateMainActivity TaskId: 44 hasCode:199785693
onCreateSecondActivity TaskId: 45 hasCode:171956091        
onCreateThirdActivity TaskId: 46 hasCode:76684615

執行adb shell dumpsys activity命令,有以下輸出:

Running activities (most recent first):
      TaskRecord{3088b56 #46 A=com.maweiqi.second U=0 sz=1}
        Run #2: ActivityRecord{9eff1ed u0 com.open.android.task1/.ThirdActivity t46}
      TaskRecord{f9bb7d7 #45 A=com.open.android.task1 U=0 sz=1}
        Run #1: ActivityRecord{16c7b28 u0 com.open.android.task1/.SecondActivity t45}
      TaskRecord{e9af8c4 #44 A=com.open.android.task1 U=0 sz=1}
        Run #0: ActivityRecord{5c2ed47 u0 com.open.android.task1/.MainActivity t44}

測試結果:

1)被SecondActivity啟動的ThirdActivity啟動在了一個新的任務中,即在啟動ThirdActivity時建立了一個新任務。這就說明被singleInstance模式的Activity A在開啟另一activity B時,能夠開啟一個新任務,但是是不是真的開啟新任務,還要受其他條件的限制,這個條件是:當前系統中是不是已經有了一個activity B的taskAffinity屬性指定的任務。

其實這種行為和singleTask啟動時的情況相同。在Activity的啟動模式設定為singleTask時,啟動時系統會為它加上FLAG_ACTIVITY_NEW_TASK標誌,而被singleInstance模式的Activity開啟的activity,啟動時系統也會為它加上FLAG_ACTIVITY_NEW_TASK標誌,所以他們啟動時的情況是相同的,上面再驗證singleTask時已經闡述過,現在重新說明一下:

結果分析:

由於ThirdActivity是被啟動模式為singleInstance型別的Activity(即SecondActivity)啟動的,framework會為它它加上FLAG_ACTIVITY_NEW_TASK標誌,這時 framework會檢索是否已經存在了一個affinity為com.maweiqi.second(即ThirdActivity的taskAffinity屬性)的任務

1)如果存在這樣的一個任務,則檢查在這個任務中是否已經有了一個ThirdActivity的例項.

1-1)如果已經存在一個ThirdActivity的例項,則會重用這個任務和任務中的ThirdActivity例項,將這個任務調到前臺,清除位於ThirdActivity上面的所有Activity,顯示ThirdActivity,並呼叫ThirdActivity的onNewIntent()。

1-2)如果不存在一個ThirdActivity的例項,會在這個任務中建立ThirdActivity的例項,並呼叫onCreate()方法

1-3)需要注意(1-1)有一種特殊情況,MainActivity, SecondActivity, ThirdActivity, FourthActivity 都不設定 taskAffinity.FourthActivity 設定為 singleInstance。測試 MainActivity -> SecondActivity -> ThirdActivity -> FourthActivity -> SecondActivity,從 FourthActivity 跳轉到 SecondActivity, 是新開的一個 SecondActivity, 不會銷燬 原 SecondActivity 上面的 ThirdActivity。

2)如果不存在這樣的一個任務,會建立一個新的affinity為com.maweiqi.second的任務,並且將ThirdActivity啟動到這個新的任務中.

如果ThirdActivity不設定taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都為應用的包名,那麼ThirdActivity是不會開啟一個新任務的.

framework中的判定過程如下:

1)在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,所以設定ThirdActivity的啟動標誌為FLAG_ACTIVITY_NEW_TASK

2)然後獲得ThirdActivity的taskAffinity,即為包名com.open.android.task1

3)檢查是否已經存在一個affinity為com.open.android.task1的任務,這個任務是存在的,就是MainActivity所在的任務,這個任務是在啟動MainActivity時開啟的

4) 既然已經存在這個任務,就檢索在這個任務中是否存在一個ThirdActivity的例項,發現不存在

5)在這個已有的任務中啟動一個SecondActivity的例項

為了作一個清楚的比較,列出ThirdActivity的taskAffinity屬性設為com.maweiqi.second時的啟動過程

1)在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,那麼設定ThirdActivity的啟動標誌為FLAG_ACTIVITY_NEW_TASK

2)然後獲得ThirdActivity的taskAffinity,即為com.maweiqi.second

3)檢查是否已經存在一個affinity為com.maweiqi.second的任務,這個任務是不存在的

4) 建立一個新的affinity為com.maweiqi.second的任務,並且將ThirdActivity啟動到這個新的任務

到此singleInstance也介紹完了。

小結

由上述可知,Task是Android Framework中的一個概念,Task是由一系列相關的Activity組成的,是一組相關Activity的集合。Task是以棧的形式來管理的。

我們在操作軟體的過程中,一定會涉及介面的跳轉。其實在對介面進行跳轉時,Android Framework既能在同一個任務中對Activity進行排程,也能以