1、爲何須要啓動模式
在Android開發中,咱們都知道,在默認的狀況下,若是咱們啓動的是同一個Activity的話,系統會建立多個實例並把它們一一放入任務棧中。當咱們點擊返回(back)鍵,這些Activity實例又將從任務棧中一一移除,遵循的原則是「後進先出」(先進後出)。android
這裏咱們考慮一個問題,當咱們屢次啓動同一個Activity,系統也會建立多個實例放入任務棧中,這樣豈不是很耗費內存資源?爲了解決這一問題,Android爲Actiivty提供了啓動模式。shell
Activity的啓動模式有四種:standard、singleTop、singleTask和singleInstance。app
2、啓動模式的分類
一、standard:標準模式
這種啓動模式爲標準模式,也是默認模式。每當咱們啓動一個Activity,系統就會相應的建立一個實例,無論這個實例是否已經存在。這種模式,一個棧中能夠有多個實例,每一個實例也都有本身的任務棧。並且是誰啓動了此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);開發
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,系統就會在當前任務棧新建一個實例。
使用場景:
正常的去打開一個新的頁面,這種啓動模式使用最多,最普通 。
二、singleTop:棧頂複用模式
這種啓動模式下,若是要啓動的Activity已經處於棧的頂部,那麼此時系統不會建立新的實例,而是直接打開此頁面,同時它的onNewIntent()方法會被執行,咱們能夠經過Intent進行傳值,並且它的onCreate(),onStart()方法不會被調用,由於它並無發生任何變化。
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的時候會執行onCreate()方法新建一個實例,而後再次啓動SingleTopActivity頁面會回調onNewIntent(),說明沒有建立新的實例,並且hashCode值沒有發生改變。此時咱們繼續打開另外一個Activity,這時OtherActivity處於棧頂,咱們繼續啓動SingleTopActivity,這時發現又是執行了onCreate(),說明又從新建立了新的實例,當咱們繼續啓動SingleTopActivity,發現回調了onNewIntent(),一樣hashCode值沒有發生改變,證實沒有從新建立實例。
分析總結:
經過上述案例概括爲如下三點:
一、當前棧中已有該Activity的實例而且該實例位於棧頂時,不會建立實例,而是複用棧頂的實例,而且會將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以下:
首先,由於發現它們的taskId值都相同,因此它們同屬於一個任務棧。其次,第一次啓動SingleTaskActivity的時候會執行onCreate()方法新建一個實例,而後再次啓動SingleTaskActivity頁面會回調onNewIntent(),說明沒有建立新的實例,並且hashCode值沒有發生改變。此時咱們繼續打開另外一個Activity,而後繼續啓動SingleTaskActivity,這時發現仍然只回調onNewIntent(),說明沒有建立新的實例,當咱們繼續啓動SingleTaskActivity,仍然只是回調了onNewIntent(),此過程當中發現hashCode值始終沒有發生改變,證實引用都是同一個的實例。
二、設置taskAffinity屬性,singleTask所在的Activity與啓動它的Activity處於不一樣的任務棧中。
<activity
android:name=".SingleTaskActivity"
android:launchMode="singleTask"
android:taskAffinity="${applicationId}.singleTask">
</activity>
指定了taskAffinity後,咱們發現除了taskId有區別外,其餘調用基本沒有什麼區別。由於MainActivity沒有指定taskAffinity屬性,默認爲包名,與SingleTaskActivity不一樣,因此在啓動SingleTaskActivity時,發現這個棧不存在,系統首先會建立這個棧而後將SingleTaskActivity壓入棧中。以後咱們發現只要棧中存在SingleTaskActivity這個實例,就會直接引用。
三、經過adb shell dumpsys activity activities查看當前運行的Activity
執行完上面的步驟,咱們經過上面的信息得出,發現只有MainActivity和SingleTaskActivity在運行,並且也能夠看出確實有1909和1910兩個任務棧。那OtherActivity哪去了?那是由於SingleTaskActivity具備ClearTop的功能,當複用SingleTashActivity的時候會將棧中SingleTaskActivity之上的Activity所有清掉,因此OtherActivity實際上是被銷燬了。
分析總結:
在複用的時候,首先會根據taskAffinity去找對應的任務棧:
一、若是不存在指定的任務棧,系統會新建對應的任務棧,並新建Activity實例壓入棧中。
二、若是存在指定的任務棧,則會查找該任務棧中是否存在該Activity實例
a、若是不存在該實例,則會在該任務棧中新建Activity實例。
b、若是存在該實例,則會直接引用,而且回調該實例的onNewIntent()方法。而且任務棧中該實例之上的Activity會被所有銷燬。
使用場景:
SingleTask這種啓動模式最常使用的就是一個APP的首頁,由於通常爲一個APP的第一個頁面,且長時間保留在棧中,因此最適合設置singleTask啓動模式來複用。
四、singleInstance:單實例模式
單實例模式,顧名思義,只有一個實例。該模式具有singleTask模式的全部特性外,與它的區別就是,這種模式下的Activity會單獨佔用一個Task棧,具備全局惟一性,即整個系統中就這麼一個實例,因爲棧內複用的特性,後續的請求均不會建立新的Activity實例,除非這個特殊的任務棧被銷燬了。以singleInstance模式啓動的Activity在整個系統中是單例的,若是在啓動這樣的Activiyt時,已經存在了一個實例,那麼會把它所在的任務調度到前臺,重用這個實例。
Manifest中配置:
<activity
android:name=".SingleInstanceActivity"
android:launchMode="singleInstance">
</activity>
使用案例:
使用上面一樣的代碼進行測試:
經過測試發現,在第一次打開SingleInstanceActivity的時候,因爲系統不存在該實例,因此係統會新建一個任務棧來存放該Activity實例,並且只要打開過一次該Activity,後面不管何時再次啓動該Activity,都會直接引用第一次建立的實例,並且會回調該實例的onNewIntent()方法。
分析總結:
啓動該模式Activity的時候,會查找系統中是否存在:
一、不存在,首先會新建一個任務棧,其次建立該Activity實例。
二、存在,則會直接引用該實例,而且回調onNewIntent()方法。
特殊狀況:該任務棧或該實例被銷燬,系統會從新建立。
使用場景:
很常見的是,電話撥號盤頁面,經過本身的應用或者其餘應用打開撥打電話頁面 ,只要系統的棧中存在該實例,那麼就會直接調用。
3、總結在使用APP過程當中,不可避免頁面之間的跳轉,那麼就會涉及到啓動模式。其實在對界面進行跳轉時,Android系統既能在同一個任務中對Activity進行調度,也能以Task(任務棧)爲單位進行總體調度。在啓動模式爲standard或singleTop時,通常是在同一個任務中對Activity進行調度,而在啓動模式爲singleTask或singleInstance是,通常會對Task進行總體調度。