為什么需要了解關於Activity的任務棧,其實最直接的體現就是提高用戶交互友好性。
舉個例子,當我們去瀏覽一個新聞客戶端的時候,我們進入了新聞詳情頁,在這個頁面有相隔兩條的新聞標題,當我們去點擊這個標題的時候進入了新的新聞詳情頁時,如果我們不加以控制會導致什么現象?它會創建出n個新聞詳細頁的Activity實例,導致用戶在退出的時候需要推出多個新聞詳情activity,這點在用戶體驗上是非常不好的,當然對於我們自身的程序也是非常不好的,不斷的去創建新的Activity必定會消耗一定的內存,久而久之,應用程序會越來越卡甚至崩潰。
在講Activity任務棧前,我們應該先知道什么是棧?
簡單點來理解,可以把棧比作一個開封的箱子,我們可以往里面塞東西,這里假設塞的東西的底面積和箱子的底面積是相同的,那么這些東西就具備有從下往上一定的順序,當我們想要取出箱子里面的東西時,我們沒有辦法一下子拿到箱子最底層的東西,我們只能拿到最上面一層的東西,從上往下。
來看下這張圖,這里的箱子就是棧,箱子口可以看作是棧的入口與出口,東西代表數據。棧的特點:具有一定的次序,后進先出(越先放入的東西,越晚出來)。
1、Activity任務棧
好了,在了解了什么是棧之后,我們可以開始進入今天的主題了,在Android的官方文檔描述中我們可以知道,任務棧也是棧,具有棧的一切特點。
Activity任務棧,顧名思義是存放Activity任務的棧,這里的任務棧為上圖箱子,Activity為上圖的東西。
當我們每打開一個Activity的時候它會就往Activity任務棧中壓入一個Activity,當我們每銷毀一個Activity的時候它會從Activity任務棧中彈出一個Activity,由於安卓系統自身的設計,我們只能在手機屏幕上獲取當前一個Activity的焦點即棧頂元素(最上面的Activity),其余的Activity會暫居后台等待系統調用。
1.1、關於任務棧的概念:
- 任務棧是用來提升體驗 而設計的:
- (1) 程序打開時就創建了一個任務棧, 用於存儲當前程序的activity,當前程序(包括被當前程序所調用的)所有的activity屬於一個任務棧。
- (2) 一個任務棧包含了一個activity的集合, 可以有序的選擇哪一個activity和用戶進行交互,只有在任務棧棧頂的activity才可以跟用戶進行交互。
- (3) 任務棧可以移動到后台,並且保留了每一個activity的狀態. 並且有序的給用戶列出它們的任務, 而且還不丟失它們狀態信息。
- (4) 退出應用程序時,當把所有的任務棧中所有的activity清除出棧時,任務棧會被銷毀,程序退出。
1.2、關於任務棧的缺點:
- (1) 每開啟一次頁面都會在任務棧中添加一個Activity,而只有任務棧中的Activity全部清除出棧時,任務棧被銷毀,程序才會退出,這樣就造成了用戶體驗差,需要點擊多次返回才可以把程序退出了。
- (2) 每開啟一次頁面都會在任務棧中添加一個Activity還會造成數據冗余 重復數據太多,會導致內存溢出的問題(OOM)。
2、Activity的4種啟動方式
為了解決任務棧產生的問題,Android為Activity設計了啟動模式,那么下面的內容將介紹Android中Activity的啟動模式,這也是最重要的內容之一。
啟動模式(launchMode)在多個Activity跳轉的過程中扮演着重要的角色,它可以解決是否生成新的Activity實例,是否重用已經存在的Activity實例,是否和其他實例共用一個任務棧。任務棧是一個具有棧結構的對象,一個任務棧可以管理多個Activity,每啟動一個應用,也就創建一個與之對應的任務棧。
-
Activity一共有以下四種launchMode模式:1、standard 2、singTop 3、singTask 4、singleInstance,我們可以在AndroidManifest.xml配置
<activity>
的android:launchMode屬性為以上四種之一即可。 - 2.1、實踐是檢驗真理的唯一標准
- 下面寫個實例,有2個Activity,每個Activity都有一個跳轉按鈕,第一個Activity點擊按鈕跳轉第二個Activity,第二個Activity點擊按鈕跳轉自身。

1 package com.lcw.rabbit.activitydemo; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.util.Log; 7 import android.view.View; 8 import android.widget.Button; 9 10 public class MainActivity extends Activity { 11 12 private static final String TAG = "Rabbit"; 13 14 private Button mbButton; 15 16 @Override 17 protected void onCreate(Bundle savedInstanceState) { 18 super.onCreate(savedInstanceState); 19 setContentView(R.layout.activity_main); 20 21 Log.i(TAG,"第一個Activity,加入任務棧:"+getTaskId()); 22 23 //點擊按鈕跳轉第二個Activity 24 mbButton = (Button) findViewById(R.id.bt_button); 25 mbButton.setOnClickListener(new View.OnClickListener() { 26 @Override 27 public void onClick(View v) { 28 startActivity(new Intent(MainActivity.this, SecondActivity.class)); 29 } 30 }); 31 32 33 } 34 35 @Override 36 protected void onDestroy() { 37 super.onDestroy(); 38 Log.i(TAG, "第一個Activity,退出任務棧:" + getTaskId()); 39 } 40 }

1 package com.lcw.rabbit.activitydemo; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.util.Log; 7 import android.view.View; 8 import android.widget.Button; 9 10 public class SecondActivity extends Activity { 11 12 private static final String TAG = "Rabbit"; 13 private Button mbButton1; 14 private Button mbButton2; 15 16 @Override 17 protected void onCreate(Bundle savedInstanceState) { 18 super.onCreate(savedInstanceState); 19 setContentView(R.layout.activity_second); 20 Log.i(TAG, "第二個Activity,加入任務棧:" + getTaskId()); 21 //點擊按鈕跳轉第二個Activity 22 mbButton1 = (Button) findViewById(R.id.bt_button1); 23 mbButton1.setOnClickListener(new View.OnClickListener() { 24 @Override 25 public void onClick(View v) { 26 startActivity(new Intent(SecondActivity.this, MainActivity.class)); 27 } 28 }); 29 mbButton2 = (Button) findViewById(R.id.bt_button2); 30 mbButton2.setOnClickListener(new View.OnClickListener() { 31 @Override 32 public void onClick(View v) { 33 startActivity(new Intent(SecondActivity.this, SecondActivity.class)); 34 } 35 }); 36 } 37 38 @Override 39 protected void onDestroy() { 40 super.onDestroy(); 41 Log.i(TAG, "第二個Activity,退出任務棧:" + getTaskId()); 42 } 43 }
實驗一:啟動模式standard
系統默認的Activity啟動模式是standard,我們這里為了檢驗再設置一下:
1 <activity 2 android:name=".MainActivity" 3 android:launchMode="standard"> 4 <intent-filter> 5 <action android:name="android.intent.action.MAIN" /> 6 <category android:name="android.intent.category.LAUNCHER" /> 7 </intent-filter> 8 </activity> 9 <activity 10 android:name=".SecondActivity" 11 android:launchMode="standard"></activity>
現在我們進入第一個Activity的時候,點擊按鈕啟動第二個Activity,看下當前任務棧:
當我們點擊第二個Activity的按鈕,讓它跳轉自身,看下當前任務棧:
當我們按下Back鍵,跳轉第一個Activity,銷毀第二個Activity時,看下當前任務棧:
可以發現,這個任務棧和我們剛上圖描述的箱子(Activity任務棧)是一致的,從上往下放東西(Activity),越晚放進去的東西(Activity)在越上面,而后面的t1072則代表當前任務棧的編號ID,ID相同代表它們屬於同一個任務棧。
我們從日志文件中也可以看得很清楚:
實驗一結論:
在Activity啟動模式為standard(默認)的情況下,不管之前有沒有Activity實例,每一次啟動Activity都會創建一個新的Activity實例,並置於Activity任務棧棧頂。
實驗二:啟動模式singleTop
1 <activity 2 android:name=".MainActivity" 3 android:launchMode="standard"> 4 <intent-filter> 5 <action android:name="android.intent.action.MAIN" /> 6 <category android:name="android.intent.category.LAUNCHER" /> 7 </intent-filter> 8 </activity> 9 <activity 10 android:name=".SecondActivity" 11 android:launchMode="singleTop"></activity>
現在我們進入第一個Activity點擊按鈕跳轉第二個Activity,然后再點擊按鈕跳轉自身,看下當前任務棧:
系統日志文件:
然后我們再點擊按鈕啟動第一個Activity,然后點擊按鈕啟動第二個Activity,看下當前任務棧:
系統日志:
實驗二結論:
在Activity啟動模式為singleTop(棧頂任務唯一)的情況下,如果當前Activity處於棧頂,那么它就不會再去實例化一個新的Activity,當Activity不處於棧頂的時候,會重新實例化一個新的Activity並置於棧頂,此時的任務棧編號為1080。
實驗三:啟動模式singleTask
1 <activity 2 android:name=".MainActivity" 3 android:launchMode="standard"> 4 <intent-filter> 5 <action android:name="android.intent.action.MAIN" /> 6 <category android:name="android.intent.category.LAUNCHER" /> 7 </intent-filter> 8 </activity> 9 <activity 10 android:name=".SecondActivity" 11 android:launchMode="singleTask"></activity>
當我們進入第一個Activity點擊進入第二個,再啟動自身,看下當前任務棧:
系統日志:
現在我們點擊啟動第一個Activity,再點擊啟動第二個Activity,看下當前任務棧:
系統日志:
實驗三結論:
在Activity啟動模式為singleTask(唯一實例)的情況下,當啟動Activity的時候,如果當前Activity不存在則實例化一個新的Activity,如果當前Activity在任務棧中已經存在,則會復用這個Activity實例,但這邊我們從日志打印可以看出在啟動第二個Activity的時候,第一個Activity推出了任務棧,也就意味着當啟動模式為singTask的時候,啟動已經存在在Activity任務棧中但不在棧頂的Activity時,該Activity會把壓在它前面的所有Activity彈出任務棧,此時任務棧編號為1081,屬於同一個任務棧。
實驗四:啟動模式singleInstance
1 <activity 2 android:name=".MainActivity" 3 android:launchMode="standard"> 4 <intent-filter> 5 <action android:name="android.intent.action.MAIN" /> 6 <category android:name="android.intent.category.LAUNCHER" /> 7 </intent-filter> 8 </activity> 9 <activity 10 android:name=".SecondActivity" 11 android:launchMode="singleInstance"></activity>
現在我們進入第一個Activity點擊按鈕跳轉第二個Activity,再讓其跳轉自身,看下當前任務棧:
系統日志:
現在點擊按鈕啟動第一個Activity,再點擊按鈕啟動第二個Activity,看下當前任務棧:
系統任務:
點擊Back鍵,直到程序完全退出,看下系統日志:
實驗四結論:
在Activity啟動模式為singleInstance的情況下,首先我們可以發現的是啟動模式為singleInstance的Activity處於不同的任務棧(Task編號不同),並保證不再有其他的Activity實例進入,它還是和singleTask一樣保持唯一實例,然后它的退出順序是不再是根據調用順序,而是在不同的任務棧中,從上往下退出。
在共用一個Activity實例時,期間發生了什么?
在上訴模式中,當我們的Activity涉及到同一實例的時候,期間Activity做了哪些事情?在Android官方文檔中我們可以知道期間雖然沒有新實例化一個Activity,但是調用了onNewIntent方法。
現在我們在第二個Activity里添加一個onNewIntent方法:
1 @Override 2 protected void onNewIntent(Intent intent) { 3 super.onNewIntent(intent); 4 Log.i(TAG, "第二個Activity,執行onNewIntent"); 5 }
1、在standard(默認)啟動模式下,我們來回的去跳轉Activity,看下日志打印,發現是不會調用onNewIntent方法的,因為它不是一個實例。
2、在singleTop模式下,我們從第一個Activity跳轉到第二個Activity,再從第二個Activity跳轉自身,再跳轉第一個Activity,看下日志打印,我們可以發現,當第二個Activity置於棧頂的時候,由於重用了實例,所以調用了onNewIntent方法。
3、當singleTask和singleInstance模式下也是一樣的,因為重用了實例,所以會調用onNewIntent方法,且onNewIntent方法是在前一個Activity的onStop方法后(當前ActivityonReStart方法前)立即調用的。