一、為什么需要啟動(dòng)模式
在Android開(kāi)發(fā)中球拦,我們都知道靠闭,在默認(rèn)的情況下帐我,如果我們啟動(dòng)的是同一個(gè)Activity的話,系統(tǒng)會(huì)創(chuàng)建多個(gè)實(shí)例并把它們一一放入任務(wù)棧中愧膀。當(dāng)我們點(diǎn)擊返回(back)鍵拦键,這些Activity實(shí)例又將從任務(wù)棧中一一移除,遵循的原則是“后進(jìn)先出”(先進(jìn)后出)檩淋。
這里我們考慮一個(gè)問(wèn)題芬为,當(dāng)我們多次啟動(dòng)同一個(gè)Activity,系統(tǒng)也會(huì)創(chuàng)建多個(gè)實(shí)例放入任務(wù)棧中蟀悦,這樣豈不是很耗費(fèi)內(nèi)存資源媚朦?為了解決這一問(wèn)題,Android為Actiivty提供了啟動(dòng)模式日戈。
Activity的啟動(dòng)模式有四種:standard询张、singleTop、singleTask和singleInstance浙炼。
二份氧、啟動(dòng)模式的分類
1、standard:標(biāo)準(zhǔn)模式
這種啟動(dòng)模式為標(biāo)準(zhǔn)模式弯屈,也是默認(rèn)模式蜗帜。每當(dāng)我們啟動(dòng)一個(gè)Activity,系統(tǒng)就會(huì)相應(yīng)的創(chuàng)建一個(gè)實(shí)例资厉,不管這個(gè)實(shí)例是否已經(jīng)存在厅缺。這種模式,一個(gè)棧中可以有多個(gè)實(shí)例宴偿,每個(gè)實(shí)例也都有自己的任務(wù)棧湘捎。而且是誰(shuí)啟動(dòng)了此Activity,那么這個(gè)Activity就運(yùn)行在啟動(dòng)它的Activity所在的棧中酪我。
Manifest中配置:
對(duì)于標(biāo)準(zhǔn)模式消痛,android:launchMode=”standard”可以不寫,因?yàn)槟J(rèn)就是standard模式都哭。
<activity
? ? android:name=".StandardActivity"
? ? android:launchMode="standard" >
</activity>
1
2
3
4
使用案例:
MainActivity有一個(gè)按鈕秩伞,點(diǎn)擊按鈕會(huì)打開(kāi)StandardActivity。打開(kāi)StandardActivity也有一個(gè)按鈕欺矫,點(diǎn)擊也是啟動(dòng)一個(gè)StandardActivity纱新。并且我們?cè)趏nCreate()方法中打印TaskId和hashCode值。
打開(kāi)步驟:MainActivity->StandardActivity->StandardActivity->StandardActivity
MainActivity:
public class MainActivity extends AppCompatActivity {
? ? private static final String TAG = MainActivity.class.getSimpleName();
? ? @Override
? ? protected void onCreate(@Nullable Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_main_demo);
? ? ? ? findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View v) {
? ? ? ? ? ? ? ? StandardActivity.open(MainActivity.this);
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",? hashCode: " + hashCode());
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
StandardActivity :
/**
* 啟動(dòng)模式:Standard(標(biāo)準(zhǔn)模式)
*/
public class StandardActivity extends AppCompatActivity {
? ? private static final String TAG = StandardActivity.class.getSimpleName();
? ? @Override
? ? protected void onCreate(@Nullable Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_launch_mode);
? ? ? ? findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View view) {
? ? ? ? ? ? ? ? open(StandardActivity.this);
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",? hashCode: " + hashCode());
? ? }
? ? public static void open(Context context) {
? ? ? ? context.startActivity(new Intent(context, StandardActivity.class));
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
控制臺(tái)打印log如下:
通過(guò)案例的log分析穆趴,可以得出標(biāo)準(zhǔn)模式下脸爱,每當(dāng)打開(kāi)一次Activity就會(huì)創(chuàng)建一個(gè)新的實(shí)例,因?yàn)閔ashCode值都不同未妹,而且都創(chuàng)建在啟動(dòng)它的Activity所屬的任務(wù)棧中簿废,也就是MainActivity所在的任務(wù)棧中空入,因?yàn)樗鼈兊娜蝿?wù)棧Id一致。
分析總結(jié):
標(biāo)準(zhǔn)模式下族檬,只要啟動(dòng)一次Activity歪赢,系統(tǒng)就會(huì)在當(dāng)前任務(wù)棧新建一個(gè)實(shí)例。
使用場(chǎng)景:
正常的去打開(kāi)一個(gè)新的頁(yè)面单料,這種啟動(dòng)模式使用最多埋凯,最普通 。
2扫尖、singleTop:棧頂復(fù)用模式
這種啟動(dòng)模式下白对,如果要啟動(dòng)的Activity已經(jīng)處于棧的頂部,那么此時(shí)系統(tǒng)不會(huì)創(chuàng)建新的實(shí)例换怖,而是直接打開(kāi)此頁(yè)面甩恼,同時(shí)它的onNewIntent()方法會(huì)被執(zhí)行,我們可以通過(guò)Intent進(jìn)行傳值狰域,而且它的onCreate()媳拴,onStart()方法不會(huì)被調(diào)用黄橘,因?yàn)樗](méi)有發(fā)生任何變化兆览。
Manifest中配置:
<activity
? ? ? android:name=".SingleTopActivity"
? ? ? android:launchMode="singleTop">
</activity>
1
2
3
4
使用案例:
MainActivity仍然是一個(gè)按鈕,點(diǎn)擊按鈕打開(kāi)SingleTopActivity塞关,SingleTopActivity有兩個(gè)按鈕抬探,一個(gè)是打開(kāi)SingleTopActivity,一個(gè)是打開(kāi)OtherActivity帆赢,OtherActivity有一個(gè)按鈕小压,點(diǎn)擊按鈕可以打開(kāi)SingleTopActivity。而且我們?cè)趏nCreate()椰于、onNewIntent()打印taskId和hashCode值怠益。
打開(kāi)步驟:MainActivity->SingleTopActivity->SingleTopActivity->OtherActivity->SingleTopActivity->SingleTopActivity
MainActivity:
public class MainActivity extends AppCompatActivity {
? ? private static final String TAG = MainActivity.class.getSimpleName();
? ? @Override
? ? protected void onCreate(@Nullable Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_main_demo);
? ? ? ? findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View v) {
? ? ? ? ? ? ? ? SingleTopActivity.open(MainActivity.this);
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",? hashCode: " + hashCode());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SingleTopActivity :
/**
* 啟動(dòng)模式:棧頂復(fù)用模式
*/
public class SingleTopActivity extends AppCompatActivity {
? ? private static final String TAG = SingleTopActivity.class.getSimpleName();
? ? @Override
? ? protected void onCreate(@Nullable Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_launch_mode);
? ? ? ? findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View view) {
? ? ? ? ? ? ? ? open(SingleTopActivity.this);
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? findViewById(R.id.btn_other).setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View view) {
? ? ? ? ? ? ? ? OtherActivity.open(SingleTopActivity.this);
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",? hashCode: " + hashCode());
? ? }
? ? @Override
? ? protected void onNewIntent(Intent intent) {
? ? ? ? super.onNewIntent(intent);
? ? ? ? Log.e(TAG, "———onNewIntent(): TaskId: " + getTaskId() +",? hashCode: " + hashCode());
? ? }
? ? public static void open(Context context) {
? ? ? ? context.startActivity(new Intent(context, SingleTopActivity.class));
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
OtherActivity:
public class OtherActivity extends AppCompatActivity {
? ? private static final String TAG = OtherActivity.class.getSimpleName();
? ? @Override
? ? protected void onCreate(@Nullable Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_other);
? ? ? ? findViewById(R.id.btn_singleTop).setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View view) {
? ? ? ? ? ? ? ? SingleTopActivity.open(OtherActivity.this);
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",? hashCode: " + hashCode());
? ? }
? ? public static void open(Context context) {
? ? ? ? context.startActivity(new Intent(context, OtherActivity.class));
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
控制臺(tái)打印log如下:
首先,因?yàn)樗鼈兊膖askId值都相同瘾婿,所以它們同屬于一個(gè)任務(wù)棧蜻牢。其次,第一次啟動(dòng)SingleTopActivity的時(shí)候會(huì)執(zhí)行onCreate()方法新建一個(gè)實(shí)例偏陪,然后再次啟動(dòng)SingleTopActivity頁(yè)面會(huì)回調(diào)onNewIntent()抢呆,說(shuō)明沒(méi)有創(chuàng)建新的實(shí)例,而且hashCode值沒(méi)有發(fā)生改變笛谦。此時(shí)我們繼續(xù)打開(kāi)另一個(gè)Activity抱虐,這時(shí)OtherActivity處于棧頂,我們繼續(xù)啟動(dòng)SingleTopActivity饥脑,這時(shí)發(fā)現(xiàn)又是執(zhí)行了onCreate()恳邀,說(shuō)明又重新創(chuàng)建了新的實(shí)例懦冰,當(dāng)我們繼續(xù)啟動(dòng)SingleTopActivity,發(fā)現(xiàn)回調(diào)了onNewIntent()谣沸,同樣hashCode值沒(méi)有發(fā)生改變冒晰,證明沒(méi)有重新創(chuàng)建實(shí)例庆捺。
分析總結(jié):
通過(guò)上述案例歸納為以下三點(diǎn):
1、當(dāng)前棧中已有該Activity的實(shí)例并且該實(shí)例位于棧頂時(shí),不會(huì)創(chuàng)建實(shí)例肖卧,而是復(fù)用棧頂?shù)膶?shí)例,并且會(huì)將Intent對(duì)象傳入嚼隘,回調(diào)onNewInten()方法保屯;
2、當(dāng)前棧中已有該Activity的實(shí)例但是該實(shí)例不在棧頂時(shí)贤重,其行為和standard啟動(dòng)模式一樣茬祷,依然會(huì)創(chuàng)建一個(gè)新的實(shí)例;
3并蝗、當(dāng)前棧中不存在該Activity的實(shí)例時(shí)祭犯,其行為同standard啟動(dòng)模式。
使用場(chǎng)景:
這種模式應(yīng)用場(chǎng)景的話滚停,假如一個(gè)新聞客戶端沃粗,在通知欄收到了3條推送,點(diǎn)擊每一條推送會(huì)打開(kāi)新聞的詳情頁(yè)键畴,如果為默認(rèn)的啟動(dòng)模式的話最盅,點(diǎn)擊一次打開(kāi)一個(gè)頁(yè)面,會(huì)打開(kāi)三個(gè)詳情頁(yè)起惕,這肯定是不合理的涡贱。如果啟動(dòng)模式設(shè)置為singleTop,當(dāng)點(diǎn)擊第一條推送后惹想,新聞詳情頁(yè)已經(jīng)處于棧頂问词,當(dāng)我們第二條和第三條推送的時(shí)候,只需要通過(guò)Intent傳入相應(yīng)的內(nèi)容即可嘀粱,并不會(huì)重新打開(kāi)新的頁(yè)面激挪,這樣就可以避免重復(fù)打開(kāi)頁(yè)面了。
3草穆、singleTask:站內(nèi)復(fù)用模式
在這個(gè)模式下灌灾,如果棧中存在這個(gè)Activity的實(shí)例就會(huì)復(fù)用這個(gè)Activity,不管它是否位于棧頂悲柱,復(fù)用時(shí)锋喜,會(huì)將它上面的Activity全部出棧,因?yàn)閟ingleTask本身自帶clearTop這種功能。并且會(huì)回調(diào)該實(shí)例的onNewIntent()方法嘿般。其實(shí)這個(gè)過(guò)程還存在一個(gè)任務(wù)棧的匹配段标,因?yàn)檫@個(gè)模式啟動(dòng)時(shí),會(huì)在自己需要的任務(wù)棧中尋找實(shí)例炉奴,這個(gè)任務(wù)棧就是通過(guò)taskAffinity屬性指定逼庞。如果這個(gè)任務(wù)棧不存在,則會(huì)創(chuàng)建這個(gè)任務(wù)棧瞻赶。不設(shè)置taskAffinity屬性的話赛糟,默認(rèn)為應(yīng)用的包名。
使用案例:
MainActivity仍然是一個(gè)按鈕砸逊,點(diǎn)擊按鈕打開(kāi)SingleTaskActivity璧南,SingleTaskActivity有兩個(gè)按鈕,一個(gè)是打開(kāi)SingleTaskActivity师逸,另一個(gè)同樣是打開(kāi)OtherActivity司倚,OtherActivity有一個(gè)按鈕,點(diǎn)擊按鈕可以打開(kāi)SingleTaskActivity篓像。同樣我們?cè)趏nCreate()动知、onNewIntent()打印taskId和hashCode值。
打開(kāi)步驟:MainActivity->SingleTaskActivity->SingleTaskActivity->OtherActivity->SingleTaskActivity->SingleTaskActivity
代碼和SingleTop基本一樣员辩,只有Manifest中配置不同盒粮,這里不再贅述。
1屈暗、不設(shè)置taskAffinity屬性拆讯,也就是默在同一個(gè)任務(wù)棧中。
Manifest中配置:
<activity
? ? android:name=".SingleTaskActivity"
? ? android:launchMode="singleTask">
</activity>
1
2
3
4
控制臺(tái)打印log如下:
首先养叛,因?yàn)榘l(fā)現(xiàn)它們的taskId值都相同,所以它們同屬于一個(gè)任務(wù)棧宰翅。其次弃甥,第一次啟動(dòng)SingleTaskActivity的時(shí)候會(huì)執(zhí)行onCreate()方法新建一個(gè)實(shí)例,然后再次啟動(dòng)SingleTaskActivity頁(yè)面會(huì)回調(diào)onNewIntent()汁讼,說(shuō)明沒(méi)有創(chuàng)建新的實(shí)例淆攻,而且hashCode值沒(méi)有發(fā)生改變。此時(shí)我們繼續(xù)打開(kāi)另一個(gè)Activity嘿架,然后繼續(xù)啟動(dòng)SingleTaskActivity瓶珊,這時(shí)發(fā)現(xiàn)仍然只回調(diào)onNewIntent(),說(shuō)明沒(méi)有創(chuàng)建新的實(shí)例耸彪,當(dāng)我們繼續(xù)啟動(dòng)SingleTaskActivity伞芹,仍然只是回調(diào)了onNewIntent(),此過(guò)程中發(fā)現(xiàn)hashCode值始終沒(méi)有發(fā)生改變,證明引用都是同一個(gè)的實(shí)例唱较。
2扎唾、設(shè)置taskAffinity屬性,singleTask所在的Activity與啟動(dòng)它的Activity處于不同的任務(wù)棧中南缓。
<activity
? ? android:name=".SingleTaskActivity"
? ? android:launchMode="singleTask"
? ? android:taskAffinity="${applicationId}.singleTask">
</activity>
1
2
3
4
5
指定了taskAffinity后胸遇,我們發(fā)現(xiàn)除了taskId有區(qū)別外,其他調(diào)用基本沒(méi)有什么區(qū)別汉形。因?yàn)镸ainActivity沒(méi)有指定taskAffinity屬性纸镊,默認(rèn)為包名,與SingleTaskActivity不同概疆,所以在啟動(dòng)SingleTaskActivity時(shí)薄腻,發(fā)現(xiàn)這個(gè)棧不存在,系統(tǒng)首先會(huì)創(chuàng)建這個(gè)棧然后將SingleTaskActivity壓入棧中届案。之后我們發(fā)現(xiàn)只要棧中存在SingleTaskActivity這個(gè)實(shí)例庵楷,就會(huì)直接引用。
3楣颠、通過(guò)adb shell dumpsys activity activities查看當(dāng)前運(yùn)行的Activity
執(zhí)行完上面的步驟尽纽,我們通過(guò)上面的信息得出,發(fā)現(xiàn)只有MainActivity和SingleTaskActivity在運(yùn)行童漩,而且也可以看出確實(shí)有1909和1910兩個(gè)任務(wù)棧弄贿。那OtherActivity哪去了?那是因?yàn)镾ingleTaskActivity具有ClearTop的功能矫膨,當(dāng)復(fù)用SingleTashActivity的時(shí)候會(huì)將棧中SingleTaskActivity之上的Activity全部清掉差凹,所以O(shè)therActivity其實(shí)是被銷毀了。
分析總結(jié):
在復(fù)用的時(shí)候侧馅,首先會(huì)根據(jù)taskAffinity去找對(duì)應(yīng)的任務(wù)棧:
1危尿、如果不存在指定的任務(wù)棧,系統(tǒng)會(huì)新建對(duì)應(yīng)的任務(wù)棧馁痴,并新建Activity實(shí)例壓入棧中谊娇。
2、如果存在指定的任務(wù)棧罗晕,則會(huì)查找該任務(wù)棧中是否存在該Activity實(shí)例
? ? ? a济欢、如果不存在該實(shí)例,則會(huì)在該任務(wù)棧中新建Activity實(shí)例小渊。
? ? ? b法褥、如果存在該實(shí)例,則會(huì)直接引用酬屉,并且回調(diào)該實(shí)例的onNewIntent()方法半等。并且任務(wù)棧中該實(shí)例之上的Activity會(huì)被全部銷毀。
使用場(chǎng)景:
SingleTask這種啟動(dòng)模式最常使用的就是一個(gè)APP的首頁(yè),因?yàn)橐话銥橐粋€(gè)APP的第一個(gè)頁(yè)面酱鸭,且長(zhǎng)時(shí)間保留在棧中吗垮,所以最適合設(shè)置singleTask啟動(dòng)模式來(lái)復(fù)用。
4凹髓、singleInstance:?jiǎn)螌?shí)例模式
單實(shí)例模式烁登,顧名思義,只有一個(gè)實(shí)例蔚舀。該模式具備singleTask模式的所有特性外饵沧,與它的區(qū)別就是,這種模式下的Activity會(huì)單獨(dú)占用一個(gè)Task棧赌躺,具有全局唯一性狼牺,即整個(gè)系統(tǒng)中就這么一個(gè)實(shí)例,由于棧內(nèi)復(fù)用的特性礼患,后續(xù)的請(qǐng)求均不會(huì)創(chuàng)建新的Activity實(shí)例是钥,除非這個(gè)特殊的任務(wù)棧被銷毀了。以singleInstance模式啟動(dòng)的Activity在整個(gè)系統(tǒng)中是單例的缅叠,如果在啟動(dòng)這樣的Activiyt時(shí)悄泥,已經(jīng)存在了一個(gè)實(shí)例,那么會(huì)把它所在的任務(wù)調(diào)度到前臺(tái)肤粱,重用這個(gè)實(shí)例弹囚。
Manifest中配置:
<activity
? ? android:name=".SingleInstanceActivity"
? ? android:launchMode="singleInstance">
</activity>
1
2
3
4
使用案例:
使用上面同樣的代碼進(jìn)行測(cè)試:
通過(guò)測(cè)試發(fā)現(xiàn),在第一次打開(kāi)SingleInstanceActivity的時(shí)候领曼,由于系統(tǒng)不存在該實(shí)例鸥鹉,所以系統(tǒng)會(huì)新建一個(gè)任務(wù)棧來(lái)存放該Activity實(shí)例,而且只要打開(kāi)過(guò)一次該Activity庶骄,后面無(wú)論什么時(shí)候再次啟動(dòng)該Activity毁渗,都會(huì)直接引用第一次創(chuàng)建的實(shí)例,而且會(huì)回調(diào)該實(shí)例的onNewIntent()方法瓢姻。
分析總結(jié):
啟動(dòng)該模式Activity的時(shí)候祝蝠,會(huì)查找系統(tǒng)中是否存在:
1、不存在幻碱,首先會(huì)新建一個(gè)任務(wù)棧,其次創(chuàng)建該Activity實(shí)例细溅。
2褥傍、存在,則會(huì)直接引用該實(shí)例喇聊,并且回調(diào)onNewIntent()方法恍风。
特殊情況:該任務(wù)棧或該實(shí)例被銷毀,系統(tǒng)會(huì)重新創(chuàng)建朋贬。
使用場(chǎng)景:
很常見(jiàn)的是凯楔,電話撥號(hào)盤頁(yè)面,通過(guò)自己的應(yīng)用或者其他應(yīng)用打開(kāi)撥打電話頁(yè)面 锦募,只要系統(tǒng)的棧中存在該實(shí)例摆屯,那么就會(huì)直接調(diào)用。
三糠亩、總結(jié)
在使用APP過(guò)程中虐骑,不可避免頁(yè)面之間的跳轉(zhuǎn),那么就會(huì)涉及到啟動(dòng)模式赎线。其實(shí)在對(duì)界面進(jìn)行跳轉(zhuǎn)時(shí)廷没,Android系統(tǒng)既能在同一個(gè)任務(wù)中對(duì)Activity進(jìn)行調(diào)度,也能以Task(任務(wù)棧)為單位進(jìn)行整體調(diào)度垂寥。在啟動(dòng)模式為standard或singleTop時(shí)颠黎,一般是在同一個(gè)任務(wù)中對(duì)Activity進(jìn)行調(diào)度,而在啟動(dòng)模式為singleTask或singleInstance是滞项,一般會(huì)對(duì)Task進(jìn)行整體調(diào)度狭归。