項(xiàng)目開發(fā)過程中遇到app每次按下home鍵再次打開都重新啟動(dòng),而不是回到之前頁面的問題(解決方法可以看這篇文章http://www.itdecent.cn/p/841c9b6d1a15),在解決過程中涉及到了activity啟動(dòng)模式的相關(guān)知識(shí),在這之前對(duì)activity四種啟動(dòng)模式有大致的了解,但一直沒有一個(gè)準(zhǔn)確、深刻的認(rèn)識(shí),只知道四種啟動(dòng)模式的存在,并不是很清楚他們之間的區(qū)別表現(xiàn)在什么地方,今天寫下這篇文章記錄一下深入學(xué)習(xí)的過程。
加深印象,增強(qiáng)理解的最好辦法莫過于寫一個(gè)demo了,通過demo我們將從打印的日志來直觀的看到四種啟動(dòng)模式的本質(zhì)區(qū)別。
首先,先看一下四種啟動(dòng)模式有哪些以及他們的概念:
1、Standard(標(biāo)準(zhǔn)模式),當(dāng)我們?cè)贏ndroidManifest.xml文件中注冊(cè)activity不設(shè)置啟動(dòng)模式時(shí),默認(rèn)的就是這種啟動(dòng)模式,當(dāng)進(jìn)入這種啟動(dòng)模式的activity時(shí),不管這個(gè)實(shí)例是否存在,都會(huì)重新創(chuàng)建一個(gè)實(shí)例放到棧頂,誰啟動(dòng)了這個(gè)activity,這個(gè)activity就屬于哪個(gè)任務(wù)棧,同一個(gè)任務(wù)棧啟動(dòng)的activity都屬于同一個(gè)任務(wù)棧,這些activity有同樣的棧id;
2、Single Top(棧頂復(fù)用模式),當(dāng)activity以這種模式啟動(dòng)時(shí),如果任務(wù)棧中沒有activity的實(shí)例則新建一個(gè),并放到棧頂,此時(shí)跟standard模式一樣;如果任務(wù)棧中有這個(gè)activity的實(shí)例但不是在棧頂,那么還是會(huì)重新創(chuàng)建一個(gè)activity實(shí)例放到棧頂,跟standard模式處理一樣;如果任務(wù)棧中已經(jīng)有了這個(gè)activity實(shí)例并且壓在棧頂,那么需要啟動(dòng)這個(gè)activity時(shí)就會(huì)直接復(fù)用這個(gè)實(shí)例,不會(huì)再重新創(chuàng)建;
3、Single Task(棧內(nèi)復(fù)用模式),當(dāng)activity以這種模式啟動(dòng)時(shí),如果任務(wù)棧已經(jīng)存在且需要啟動(dòng)的activity也已經(jīng)存在于這個(gè)棧內(nèi),那么無論activity實(shí)例是否位于棧頂,都不會(huì)重新創(chuàng)建,而是復(fù)用這個(gè)activity。如果此時(shí)activity不位于棧頂,那么位于需要啟動(dòng)的activity上面的activity都會(huì)出棧,使得需要啟動(dòng)的activity位于棧頂;如果任務(wù)?;騛ctivity指定的任務(wù)棧不存在,那么會(huì)重新創(chuàng)建一個(gè)任務(wù)棧,然后創(chuàng)建一個(gè)新的activity實(shí)例進(jìn)入棧中。
此外,如果在兩個(gè)不同的應(yīng)用中activity指定的任務(wù)棧一樣,那么啟動(dòng)的activity也會(huì)進(jìn)入到同一個(gè)棧中;
4、Single Instance(全局唯一模式),這種模式下的activity除了擁有Single Task所有特性外,在系統(tǒng)內(nèi)全局唯一,因?yàn)榇藭r(shí)activity會(huì)單獨(dú)占用一個(gè)任務(wù)棧,棧內(nèi)復(fù)用的特性使得任務(wù)棧如果沒有被銷毀,就不會(huì)去重新創(chuàng)建activity實(shí)例,此時(shí)會(huì)將任務(wù)棧調(diào)度到前臺(tái)復(fù)用。
上面4種啟動(dòng)模式中,前兩種standard和single top模式都是在同一個(gè)棧內(nèi)進(jìn)行activity的創(chuàng)建和復(fù)用;后兩種single task和single instance模式會(huì)創(chuàng)建新的任務(wù)棧。
下面,我們通過一個(gè)demo來加強(qiáng)對(duì)四種啟動(dòng)模式的認(rèn)識(shí)。
我們將每一個(gè)activity創(chuàng)建過程打印在控制臺(tái)上,這樣可以直觀顯示activity的創(chuàng)建過程,更便于我們理解,所以這里建了一個(gè)基類BaseActivity,里面主要做一個(gè)日志打印操作和一個(gè)跳轉(zhuǎn)操作,代碼如下:
/**
* activity基類,主要是對(duì)activity啟動(dòng)時(shí)調(diào)用方法做一個(gè)打印
* 打印內(nèi)容包括activity的hashcode以及所屬的task
*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("TAG", "onCreate()");
Log.e("TAG", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e("TAG", "onNewIntent()");
Log.e("TAG", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
/**
* 打印任務(wù)棧信息
*/
private void dumpTaskAffinity() {
try {
ActivityInfo info = this.getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.e("TAG", "taskAffinity:"+info.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
/**
* 頁面跳轉(zhuǎn)
*/
protected void forwordToActivity(Context context,Class<? extends BaseActivity> clazz){
Intent intent = new Intent(context,clazz);
context.startActivity(intent);
}
MainActivity類做為各個(gè)activity的入口,在MainActivity中點(diǎn)擊對(duì)應(yīng)的按鈕進(jìn)入到對(duì)應(yīng)的activity,布局和代碼如下:

public class MainActivity extends BaseActivity implements View.OnClickListener {
private Button mStandardBtn;
private Button mSingleTopBtn;
private Button mSingleTaskBtn;
private Button mSingleInstanceBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mStandardBtn = findViewById(R.id.standard_btn);
mSingleTopBtn = findViewById(R.id.single_top_btn);
mSingleTaskBtn = findViewById(R.id.single_task_btn);
mSingleInstanceBtn = findViewById(R.id.single_instance_btn);
mStandardBtn.setOnClickListener(this);
mSingleTopBtn.setOnClickListener(this);
mSingleTaskBtn.setOnClickListener(this);
mSingleInstanceBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.standard_btn://standard模式
forwordToActivity(MainActivity.this,ActivityStandard.class);
break;
case R.id.single_top_btn://single top模式
forwordToActivity(MainActivity.this,ActivitySingleTop.class);
break;
case R.id.single_task_btn://single task模式
forwordToActivity(MainActivity.this,ActivitySingleTask.class);
break;
case R.id.single_instance_btn://single instance模式
Intent intent = new Intent();
intent.setAction("com.example.demo.singleinstance");
startActivity(intent);
break;
}
}
}
接下來我們先看第一種啟動(dòng)模式——standard
這種模式的activity不需要專門去設(shè)置,默認(rèn)就是這個(gè),當(dāng)然你也可以在AndroidManifest.xml中注冊(cè)activity時(shí)加上一下代碼,將其啟動(dòng)模式設(shè)置為standard。
android:launchMode="standard"
建了一個(gè)以standard啟動(dòng)的ActivityStandard繼承自基類BaseActivity,先貼上布局和代碼:

public class ActivityStandard extends BaseActivity{
private Button mSecondToStandard;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_standard);
initView();
}
private void initView() {
mSecondToStandard = findViewById(R.id.second_jump_standard);
mSecondToStandard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//再次進(jìn)入到本頁面
forwordToActivity(ActivityStandard.this,ActivityStandard.class);
}
});
}
}
布局有一個(gè)按鈕,點(diǎn)擊按鈕實(shí)現(xiàn)再次跳轉(zhuǎn)進(jìn)入到本頁面。
操作流程:程序運(yùn)行起來,從MainActivity點(diǎn)擊進(jìn)入到以standard模式啟動(dòng)的ActivityStandard頁面,然后再點(diǎn)擊該頁面內(nèi)的按鈕三次。根據(jù)控制臺(tái)打印的內(nèi)容看看系統(tǒng)在這個(gè)流程中做了什么操作,控制臺(tái)打印日志如下:

從控制臺(tái)可以看到當(dāng)activity以standard模式啟動(dòng)時(shí),從MainActivity進(jìn)入到ActivityStandard頁面,執(zhí)行了onCreate()方法,此時(shí)任務(wù)棧中已經(jīng)有MainActivity和ActivityStandard兩個(gè)activity存在,之后在ActivityStandard頁面進(jìn)行的三次點(diǎn)擊事件,根據(jù)他們的hashcode可以看出,三次跳轉(zhuǎn)又創(chuàng)建三個(gè)activity實(shí)例,在這個(gè)過程中,他們的taskId都是一樣的,表明他們位于同一個(gè)棧內(nèi)。在之后的操作中按返回鍵需要按三次才會(huì)回到MainActivity,這也佐證了standard模式下,無論需要啟動(dòng)的activity是否已經(jīng)存在,每一次都會(huì)重新創(chuàng)建一個(gè)實(shí)例位于棧頂。
第二種棧頂復(fù)用啟動(dòng)模式——Single Top,ActivitySingleTop布局和代碼如下:

public class ActivitySingleTop extends BaseActivity{
private Button mSecondToSingleTop;
private Button mToOther;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_top);
initView();
}
private void initView() {
mSecondToSingleTop = findViewById(R.id.second_jump_single_top);
mToOther = findViewById(R.id.to_other_activity);
mSecondToSingleTop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
forwordToActivity(ActivitySingleTop.this,ActivitySingleTop.class);
}
});
mToOther.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
forwordToActivity(ActivitySingleTop.this,OtherActivity.class);
}
});
}
}
在ActivitySingleTop布局中有兩個(gè)按鈕,點(diǎn)擊single top second是再次跳轉(zhuǎn)到這個(gè)頁面,點(diǎn)擊to other activity是跳轉(zhuǎn)到其他頁面,目的在于跳轉(zhuǎn)到其他頁面后,ActivitySingleTop就不在棧頂,這時(shí)再跳轉(zhuǎn)到ActivitySingleTop頁面,通過日志看看不在棧頂?shù)腁ctivitySingleTop會(huì)怎樣。
接下來先執(zhí)行第一個(gè)流程:進(jìn)入到MainActivity后點(diǎn)擊進(jìn)入到ActivitySingleTop,同樣點(diǎn)擊三次(single top second)按鈕,做三次跳轉(zhuǎn),看看日志打印的過程:

從日志中我們可以看到,除了第一次從MainActivity跳轉(zhuǎn)進(jìn)入到ActivitySingleTop時(shí)調(diào)用了onCreate()方法,之后三次再跳轉(zhuǎn)到ActivitySingleTop時(shí),調(diào)用的onNewIntent()方法,而且我們可以看到,四次跳轉(zhuǎn)的taskId是一樣的,表明啟動(dòng)的activity始終是在同一個(gè)任務(wù)棧中,而且hashcode也一樣,表明activity實(shí)例也是同一個(gè),當(dāng)activity已經(jīng)存在與棧中(此時(shí)ActivitySingleTop存在于棧頂)的時(shí)候,不會(huì)重新創(chuàng)建實(shí)例而是復(fù)用之前的。
此時(shí),你如果按返回鍵的話,只需要按一次就會(huì)返回到MainActivity頁面,不會(huì)想standard模式那樣,你跳轉(zhuǎn)了幾次就需要按幾次返回才能返回到首頁,這也證明了single top的機(jī)制,當(dāng)啟動(dòng)的activity位于棧頂時(shí),再次啟動(dòng)將會(huì)被復(fù)用。
上面的流程只能說明activity位于棧頂?shù)臅r(shí)候會(huì)得到復(fù)用,如果不是位于棧頂會(huì)怎樣呢,接下來,我們接著驗(yàn)證。操作流程:從MainActivity進(jìn)入ActivitySingleTop,點(diǎn)擊ActivitySingleTop頁面內(nèi)的(to other activity)按鈕一次進(jìn)入到其他頁面,然后再點(diǎn)擊其他頁面內(nèi)的(back)按鈕再跳轉(zhuǎn)到ActivitySingleTop,最后再點(diǎn)擊(single top second)按鈕。OtherActivity的布局和代碼如下:

public class OtherActivity extends BaseActivity{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
Button backBtn = findViewById(R.id.back_btn);
backBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//跳轉(zhuǎn)到ActivitySingleTop頁面
forwordToActivity(OtherActivity.this,ActivitySingleTop.class);
//跳轉(zhuǎn)到ActivitySingleTask頁面
// forwordToActivity(OtherActivity.this,ActivitySingleTask.class);
}
});
}
}
看一下操作過程中打印的日志:

在這個(gè)過程中,第一次跳轉(zhuǎn)從MainActivity進(jìn)入到ActivitySingleTop,此時(shí)棧中有兩個(gè)activity,且ActivitySingleTop位于棧頂。之后從ActivitySingleTop跳轉(zhuǎn)進(jìn)入到OtherActivity中,此時(shí)棧中有三個(gè)activity,OtherActivity位于棧頂,ActivitySingleTop位于中間,最下面是MainActivity。然后從OtherActivity再跳轉(zhuǎn)到ActivitySingleTop,從日志可以看出,在做這次跳轉(zhuǎn)前,棧中雖然有ActivitySingleTop這個(gè)實(shí)例,但不在棧頂,所以當(dāng)再次跳轉(zhuǎn)進(jìn)入到這個(gè)activity時(shí),它的hashcode和第一個(gè)(從MainActivity跳轉(zhuǎn)進(jìn)入時(shí)創(chuàng)建)不同,證明此時(shí)又重新創(chuàng)建了一個(gè)實(shí)例,這次跳轉(zhuǎn)后ActivitySingleTop位于棧頂了,此時(shí)再做進(jìn)入這個(gè)頁面的跳轉(zhuǎn),就不會(huì)再重新創(chuàng)建實(shí)例了,日志上變現(xiàn)為,最后兩次的hashcode值一樣。
此外從日志中能看到四次跳轉(zhuǎn)的taskId都是一樣的,表明從始至終都是再同一個(gè)棧內(nèi)進(jìn)行操作的。
如果此時(shí)按返回鍵,那么你會(huì)發(fā)現(xiàn)返回的順序是ActivitySingleTop->OtherActivity->ActivitySingleTop->MainActivity,這也說明了single top棧頂復(fù)用的機(jī)制,當(dāng)啟動(dòng)的activity不為于棧頂時(shí)去啟動(dòng)他,就會(huì)重新創(chuàng)建一個(gè)位于棧頂,將之前的activity壓到下面去。
第三種棧內(nèi)復(fù)用啟動(dòng)模式——Single Task,同樣先貼出布局和代碼:

public class ActivitySingleTask extends BaseActivity{
private Button mSecondToSingleTask;
private Button mToOther;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_task);
initView();
}
private void initView() {
mSecondToSingleTask = findViewById(R.id.second_jump_single_task);
mToOther = findViewById(R.id.to_activity_other);
mSecondToSingleTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
forwordToActivity(ActivitySingleTask.this,ActivitySingleTask.class);
}
});
mToOther.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
forwordToActivity(ActivitySingleTask.this,OtherActivity.class);
}
});
}
}
這個(gè)模式我們主要驗(yàn)證兩點(diǎn),一是驗(yàn)證當(dāng)ActivitySingleTask不是位于棧頂時(shí),再次跳轉(zhuǎn)時(shí)是否會(huì)得到復(fù)用;二是當(dāng)設(shè)置的任務(wù)棧不存在時(shí),會(huì)怎么樣?
先來驗(yàn)證第一個(gè),先從MainActivity進(jìn)入到ActivitySingleTask,然后進(jìn)入到OtherActivity,之后再進(jìn)入到ActivitySingleTask,最后再做一次同一個(gè)頁面的跳轉(zhuǎn)。
控制臺(tái)輸出的日志如下:

操作流程和single top的第二個(gè)驗(yàn)證差不多,所以日志我們可以對(duì)比這來看,前面兩次跳轉(zhuǎn)分別是ActivitySingleTask的創(chuàng)建和OtherActivity的創(chuàng)建,經(jīng)過前兩次的跳轉(zhuǎn),棧中已經(jīng)有了三個(gè)activity,其中OtherActivity位于棧頂,ActivitySingleTask位于中間,MainActivity位于棧底,第三次跳轉(zhuǎn)的時(shí)候,沒有像single top那樣調(diào)用onCreate()方法而是調(diào)用的onNewIntent()方法,且hashcode值跟第一次創(chuàng)建的也一樣,所以從這可以看出,single task模式啟動(dòng)的activity沒有位于棧頂,當(dāng)再次啟動(dòng)時(shí),都會(huì)得到復(fù)用,第三次跳轉(zhuǎn)后ActivitySingleTask位于棧頂了,此時(shí)再做同頁面的跳轉(zhuǎn),復(fù)用也就毋庸置疑了。
此時(shí),你點(diǎn)擊返回鍵,你會(huì)發(fā)現(xiàn),點(diǎn)擊一次就直接返回到了MainActivity,并沒有出現(xiàn)返回到OtherActivity的情況,這也說明了single task模式下啟動(dòng)的activity在得到復(fù)用時(shí),如果不是位于棧頂,那么他上面的activity將被出棧,使得復(fù)用的activity位于棧頂。
通過日志我們可以看出,到目前位置,每個(gè)驗(yàn)證流程他們的taskId都是一樣的,都是在同一個(gè)任務(wù)棧中進(jìn)行的,那么當(dāng)指定的任務(wù)棧不存在時(shí),會(huì)怎樣呢?接下來我們驗(yàn)證第二個(gè)點(diǎn):
首先我們需要給啟動(dòng)的activity設(shè)置一個(gè)沒有創(chuàng)建過的任務(wù)棧,在注冊(cè)的ActivitySingleTask下通過如下代碼來設(shè)置啟動(dòng)它的任務(wù)棧:
android:taskAffinity="com.example.yfsl.activitystartmode_demo.singletask"
我們?cè)僬罩厦娴牧鞒滩僮饕槐?,看看日志的變化?br>

從日志中可以看到,剛打開應(yīng)用進(jìn)入到MainActivity時(shí)創(chuàng)建了一個(gè)任務(wù)棧,由于沒有指定,此時(shí)的任務(wù)棧名就是包名,點(diǎn)擊進(jìn)入到ActivitySingleTask時(shí),因?yàn)橹霸贏ndroidManifest.xml中聲明了啟動(dòng)ActivitySingleTask的任務(wù)棧,所以在日志中可以看到,啟動(dòng)ActivitySingleTask時(shí)新創(chuàng)建了一個(gè)棧,并且之后的各種跳轉(zhuǎn)都是在這個(gè)棧中進(jìn)行的,taskId都是80,這說明當(dāng)啟動(dòng)activity的任務(wù)棧不存在時(shí),會(huì)去創(chuàng)建一個(gè)任務(wù)棧,并且之后的操作在這個(gè)任務(wù)棧沒被銷毀時(shí)都會(huì)在其中進(jìn)行。
最后一種全局唯一啟動(dòng)模式——single instance,這種模式下我們主要驗(yàn)證他的全局唯一性,所以需要再建一個(gè)應(yīng)用,應(yīng)用主要是跟之前的應(yīng)用一樣,跳到同一個(gè)頁面去,看看不同應(yīng)用之間,以single instance啟動(dòng)同一個(gè)頁面會(huì)不會(huì)得到復(fù)用。
在AndroidMnifest.xml中注冊(cè)ActivitySingleInstance時(shí)配置代碼如下:
<activity
android:name=".ActivitySingleInstance"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.example.demo.singleinstance"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
在MainActivity中跳轉(zhuǎn)代碼為:
Intent intent = new Intent();
//設(shè)置action作為一個(gè)標(biāo)記
intent.setAction("com.example.demo.singleinstance");
startActivity(intent);
在新建的應(yīng)用內(nèi),處理與這個(gè)應(yīng)用中完全一樣。操作流程是,先打開最開始的那個(gè)應(yīng)用,點(diǎn)擊進(jìn)入到ActivitySingleInstance頁面,然后按home鍵,再打開新建的應(yīng)用,點(diǎn)擊進(jìn)入到ActivitySingleInstance頁面。
在這個(gè)過程中遇到了一個(gè)問題,就是在兩次點(diǎn)擊跳轉(zhuǎn)的時(shí)候都會(huì)讓我選擇應(yīng)用,如果兩次選擇的應(yīng)用是一樣的,那么就會(huì)復(fù)用,并且是跳轉(zhuǎn)到同一個(gè)應(yīng)用中;如果兩次選擇的不一樣,那么就不會(huì)出現(xiàn)復(fù)用的情況,沒有體現(xiàn)出全局唯一性,下面貼出兩次選擇的日志:



single instance模式啟動(dòng)的activity在兩個(gè)應(yīng)用之間進(jìn)行跳轉(zhuǎn)時(shí)出現(xiàn)的這個(gè)問題,貌似并沒有體現(xiàn)出它的全局唯一性,而且如果在第二個(gè)應(yīng)用中做跳轉(zhuǎn)時(shí)選擇的是第一個(gè)應(yīng)用的話,那么會(huì)跳轉(zhuǎn)到第一個(gè)應(yīng)用中去。希望大佬看到這篇文章知道這個(gè)問題能留言解惑,萬分感謝。
好了,以上就是我對(duì)activity四種啟動(dòng)模式的認(rèn)識(shí)和理解,我是在徹底弄懂Activity四大啟動(dòng)模式這篇文章的基礎(chǔ)上學(xué)習(xí)的,在此鳴謝。對(duì)了,demo在這里,可以下載下來看看文末描述的問題,交流一波。