Android開發(fā)學習教程(24)- Android中Activity的啟動模式(LaunchMode)和使用場景
1、為何須要啟動模式
在Android開發(fā)中,咱們都知道,在默認的狀況下,若是咱們啟動的是同一個Activity的話,系統(tǒng)會建立多個實例并把它們一一放入任務棧中。當咱們點擊返回(back)鍵,這些Activity實例又將從任務棧中一一移除,遵循的原則是“后進先出”(先進后出)。android
這里咱們考慮一個問題,當咱們屢次啟動同一個Activity,系統(tǒng)也會建立多個實例放入任務棧中,這樣豈不是很耗費內存資源?為了解決這一問題,Android為Actiivty提供了啟動模式。shell
Activity的啟動模式有四種:standard、singleTop、singleTask和singleInstance。app
2、啟動模式的分類
一、standard:標準模式
這種啟動模式為標準模式,也是默認模式。每當咱們啟動一個Activity,系統(tǒng)就會相應的建立一個實例,無論這個實例是否已經存在。這種模式,一個棧中能夠有多個實例,每一個實例也都有本身的任務棧。并且是誰啟動了此Activity,那么這個Activity就運行在啟動它的Activity所在的棧中。ide
Manifest中配置:
對于標準模式,android:launchMode=”standard”能夠不寫,由于默認就是standard模式。
<activity
android:name=”.StandardActivity”
android:launchMode=”standard” >
</activity>
測試
使用案例:
MainActivity有一個按鈕,點擊按鈕會打開StandardActivity。打開StandardActivity也有一個按鈕,點擊也是啟動一個StandardActivity。而且咱們在onCreate()方法中打印TaskId和hashCode值。
打開步驟:MainActivity->StandardActivity->StandardActivity->StandardActivitythis
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);開發(fā)
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());
}
}
StandardActivity :
/**
* 啟動模式:Standard(標準模式)
*/
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));
}
}
控制臺打印log以下:
經過案例的log分析,能夠得出標準模式下,每當打開一次Activity就會建立一個新的實例,由于hashCode值都不一樣,并且都建立在啟動它的Activity所屬的任務棧中,也就是MainActivity所在的任務棧中,由于它們的任務棧Id一致。
分析總結:
標準模式下,只要啟動一次Activity,系統(tǒng)就會在當前任務棧新建一個實例。
使用場景:
正常的去打開一個新的頁面,這種啟動模式使用最多,最普通 。
二、singleTop:棧頂復用模式
這種啟動模式下,若是要啟動的Activity已經處于棧的頂部,那么此時系統(tǒng)不會建立新的實例,而是直接打開此頁面,同時它的onNewIntent()方法會被執(zhí)行,咱們能夠經過Intent進行傳值,并且它的onCreate(),onStart()方法不會被調用,由于它并無發(fā)生任何變化。
Manifest中配置:
<activity
android:name=”.SingleTopActivity”
android:launchMode=”singleTop”>
</activity>
1
2
3
4
使用案例:
MainActivity仍然是一個按鈕,點擊按鈕打開SingleTopActivity,SingleTopActivity有兩個按鈕,一個是打開SingleTopActivity,一個是打開OtherActivity,OtherActivity有一個按鈕,點擊按鈕能夠打開SingleTopActivity。并且咱們在onCreate()、onNewIntent()打印taskId和hashCode值。
打開步驟: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());
}
SingleTopActivity :
/**
* 啟動模式:棧頂復用模式
*/
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));
}
}
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));
}
}
控制臺打印log以下:
首先,由于它們的taskId值都相同,因此它們同屬于一個任務棧。其次,第一次啟動SingleTopActivity的時候會執(zhí)行onCreate()方法新建一個實例,而后再次啟動SingleTopActivity頁面會回調onNewIntent(),說明沒有建立新的實例,并且hashCode值沒有發(fā)生改變。此時咱們繼續(xù)打開另外一個Activity,這時OtherActivity處于棧頂,咱們繼續(xù)啟動SingleTopActivity,這時發(fā)現(xiàn)又是執(zhí)行了onCreate(),說明又從新建立了新的實例,當咱們繼續(xù)啟動SingleTopActivity,發(fā)現(xiàn)回調了onNewIntent(),一樣hashCode值沒有發(fā)生改變,證實沒有從新建立實例。
分析總結:
經過上述案例概括為如下三點:
一、當前棧中已有該Activity的實例而且該實例位于棧頂時,不會建立實例,而是復用棧頂?shù)膶嵗?,而且會將Intent對象傳入,回調onNewInten()方法;
二、當前棧中已有該Activity的實例可是該實例不在棧頂時,其行為和standard啟動模式同樣,依然會建立一個新的實例;
三、當前棧中不存在該Activity的實例時,其行為同standard啟動模式。
使用場景:
這種模式應用場景的話,假如一個新聞客戶端,在通知欄收到了3條推送,點擊每一條推送會打開新聞的詳情頁,若是為默認的啟動模式的話,點擊一次打開一個頁面,會打開三個詳情頁,這確定是不合理的。若是啟動模式設置為singleTop,當點擊第一條推送后,新聞詳情頁已經處于棧頂,當咱們第二條和第三條推送的時候,只須要經過Intent傳入相應的內容便可,并不會從新打開新的頁面,這樣就能夠避免重復打開頁面了。
三、singleTask:站內復用模式
在這個模式下,若是棧中存在這個Activity的實例就會復用這個Activity,無論它是否位于棧頂,復用時,會將它上面的Activity所有出棧,由于singleTask自己自帶clearTop這種功能。而且會回調該實例的onNewIntent()方法。其實這個過程還存在一個任務棧的匹配,由于這個模式啟動時,會在本身須要的任務棧中尋找實例,這個任務棧就是經過taskAffinity屬性指定。若是這個任務棧不存在,則會建立這個任務棧。不設置taskAffinity屬性的話,默認為應用的包名。
使用案例:
MainActivity仍然是一個按鈕,點擊按鈕打開SingleTaskActivity,SingleTaskActivity有兩個按鈕,一個是打開SingleTaskActivity,另外一個一樣是打開OtherActivity,OtherActivity有一個按鈕,點擊按鈕能夠打開SingleTaskActivity。一樣咱們在onCreate()、onNewIntent()打印taskId和hashCode值。
打開步驟:MainActivity->SingleTaskActivity->SingleTaskActivity->OtherActivity->SingleTaskActivity->SingleTaskActivity
代碼和SingleTop基本同樣,只有Manifest中配置不一樣,這里再也不贅述。
一、不設置taskAffinity屬性,也就是默在同一個任務棧中。
Manifest中配置:
<activity
android:name=”.SingleTaskActivity”
android:launchMode=”singleTask”>
</activity>
1
2
3
4
控制臺打印log以下:
首先,由于發(fā)現(xiàn)它們的taskId值都相同,因此它們同屬于一個任務棧。其次,第一次啟動SingleTaskActivity的時候會執(zhí)行onCreate()方法新建一個實例,而后再次啟動SingleTaskActivity頁面會回調onNewIntent(),說明沒有建立新的實例,并且hashCode值沒有發(fā)生改變。此時咱們繼續(xù)打開另外一個Activity,而后繼續(xù)啟動SingleTaskActivity,這時發(fā)現(xiàn)仍然只回調onNewIntent(),說明沒有建立新的實例,當咱們繼續(xù)啟動SingleTaskActivity,仍然只是回調了onNewIntent(),此過程當中發(fā)現(xiàn)hashCode值始終沒有發(fā)生改變,證實引用都是同一個的實例。
二、設置taskAffinity屬性,singleTask所在的Activity與啟動它的Activity處于不一樣的任務棧中。
<activity
android:name=”.SingleTaskActivity”
android:launchMode=”singleTask”
android:taskAffinity=”${applicationId}.singleTask”>
</activity>
指定了taskAffinity后,咱們發(fā)現(xiàn)除了taskId有區(qū)別外,其余調用基本沒有什么區(qū)別。由于MainActivity沒有指定taskAffinity屬性,默認為包名,與SingleTaskActivity不一樣,因此在啟動SingleTaskActivity時,發(fā)現(xiàn)這個棧不存在,系統(tǒng)首先會建立這個棧而后將SingleTaskActivity壓入棧中。以后咱們發(fā)現(xiàn)只要棧中存在SingleTaskActivity這個實例,就會直接引用。
三、經過adb shell dumpsys activity activities查看當前運行的Activity
執(zhí)行完上面的步驟,咱們經過上面的信息得出,發(fā)現(xiàn)只有MainActivity和SingleTaskActivity在運行,并且也能夠看出確實有1909和1910兩個任務棧。那OtherActivity哪去了?那是由于SingleTaskActivity具備ClearTop的功能,當復用SingleTashActivity的時候會將棧中SingleTaskActivity之上的Activity所有清掉,因此OtherActivity實際上是被銷毀了。
分析總結:
在復用的時候,首先會根據(jù)taskAffinity去找對應的任務棧:
一、若是不存在指定的任務棧,系統(tǒng)會新建對應的任務棧,并新建Activity實例壓入棧中。
二、若是存在指定的任務棧,則會查找該任務棧中是否存在該Activity實例
a、若是不存在該實例,則會在該任務棧中新建Activity實例。
b、若是存在該實例,則會直接引用,而且回調該實例的onNewIntent()方法。而且任務棧中該實例之上的Activity會被所有銷毀。
使用場景:
SingleTask這種啟動模式最常使用的就是一個APP的首頁,由于通常為一個APP的第一個頁面,且長時間保留在棧中,因此最適合設置singleTask啟動模式來復用。
四、singleInstance:單實例模式
單實例模式,顧名思義,只有一個實例。該模式具有singleTask模式的全部特性外,與它的區(qū)別就是,這種模式下的Activity會單獨占用一個Task棧,具備全局惟一性,即整個系統(tǒng)中就這么一個實例,因為棧內復用的特性,后續(xù)的請求均不會建立新的Activity實例,除非這個特殊的任務棧被銷毀了。以singleInstance模式啟動的Activity在整個系統(tǒng)中是單例的,若是在啟動這樣的Activiyt時,已經存在了一個實例,那么會把它所在的任務調度到前臺,重用這個實例。
Manifest中配置:
<activity
android:name=”.SingleInstanceActivity”
android:launchMode=”singleInstance”>
</activity>
使用案例:
使用上面一樣的代碼進行測試:
經過測試發(fā)現(xiàn),在第一次打開SingleInstanceActivity的時候,因為系統(tǒng)不存在該實例,因此系統(tǒng)會新建一個任務棧來存放該Activity實例,并且只要打開過一次該Activity,后面不管何時再次啟動該Activity,都會直接引用第一次建立的實例,并且會回調該實例的onNewIntent()方法。
分析總結:
啟動該模式Activity的時候,會查找系統(tǒng)中是否存在:
一、不存在,首先會新建一個任務棧,其次建立該Activity實例。
二、存在,則會直接引用該實例,而且回調onNewIntent()方法。
特殊狀況:該任務?;蛟搶嵗讳N毀,系統(tǒng)會從新建立。
使用場景:
很常見的是,電話撥號盤頁面,經過本身的應用或者其余應用打開撥打電話頁面 ,只要系統(tǒng)的棧中存在該實例,那么就會直接調用。
3、總結在使用APP過程當中,不可避免頁面之間的跳轉,那么就會涉及到啟動模式。其實在對界面進行跳轉時,Android系統(tǒng)既能在同一個任務中對Activity進行調度,也能以Task(任務棧)為單位進行總體調度。在啟動模式為standard或singleTop時,通常是在同一個任務中對Activity進行調度,而在啟動模式為singleTask或singleInstance是,通常會對Task進行總體調度。