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。
輸出的日誌如下:
onCreate:MainActivity TaskId: 21 hasCode:199785693
onCreate:SecondActivity 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。
輸出的日誌如下:
onCreate:MainActivity TaskId: 24 hasCode:199785693
onCreate:SecondActivity TaskId: 25 hasCode:171956091
onCreate:ThirdActivity 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應用測試日誌輸出***
onCreate:MainActivity TaskId: 29 hasCode:193251508
onCreate:SecondActivity TaskId: 30 hasCode:171956091
***AndroidTaskTest3應用測試日誌輸出***
onCreate:MainActivity TaskId: 31 hasCode:199785693
onCreate:OtherActivity 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
日誌輸出
onCreate:MainActivity TaskId: 34 hasCode:199785693
onCreate:SecondActivity TaskId: 34 hasCode:171956091
onCreate:ThirdActivity TaskId: 34 hasCode:240438941
onCreate:FourthActivity 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輸出:
日誌輸出
onCreate:MainActivity TaskId: 35 hasCode:199785693
onCreate:SecondActivity 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
日誌輸出:
onCreate:MainActivity TaskId: 42 hasCode:199785693
onCreate:SecondActivity TaskId: 43 hasCode:171956091
onCreate:ThirdActivity 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。可以看到有如下輸出:
onCreate:MainActivity TaskId: 44 hasCode:199785693
onCreate:SecondActivity TaskId: 45 hasCode:171956091
onCreate:ThirdActivity 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進行排程,也能以