Activity-任務棧和啓動模式

 爲何須要了解關於Activity的任務棧,其實最直接的體現就是提升用戶交互友好性。android

  舉個例子,當咱們去瀏覽一個新聞客戶端的時候,咱們進入了新聞詳情頁,在這個頁面有相隔兩條的新聞標題,當咱們去點擊這個標題的時候進入了新的新聞詳情頁時,若是咱們不加以控制會致使什麼現象?它會建立出n個新聞詳細頁的Activity實例,致使用戶在退出的時候須要推出多個新聞詳情activity,這點在用戶體驗上是很是很差的,固然對於咱們自身的程序也是很是很差的,不斷的去建立新的Activity一定會消耗必定的內存,長此以往,應用程序會愈來愈卡甚至崩潰。
app

  

在講Activity任務棧前,咱們應該先知道什麼是棧?ide

  簡單點來理解,能夠把棧比做一個開封的箱子,咱們能夠往裏面塞東西,這裏假設塞的東西的底面積和箱子的底面積是相同的,那麼這些東西就具有有從下往上必定的順序,當咱們想要取出箱子裏面的東西時,咱們沒有辦法一會兒拿到箱子最底層的東西,咱們只能拿到最上面一層的東西,從上往下。this

  來看下這張圖,這裏的箱子就是棧,箱子口能夠看做是棧的入口與出口,東西表明數據。棧的特色:具備必定的次序,後進先出(越先放入的東西,越晚出來)。spa

一、Activity任務棧  設計

 好了,在瞭解了什麼是棧以後,咱們能夠開始進入今天的主題了,在Android的官方文檔描述中咱們能夠知道,任務棧也是棧,具備棧的一切特色。3d

  Activity任務棧,顧名思義是存放Activity任務的棧,這裏的任務棧爲上圖箱子,Activity爲上圖的東西。日誌

  當咱們每打開一個Activity的時候它會就往Activity任務棧中壓入一個Activity,當咱們每銷燬一個Activity的時候它會從Activity任務棧中彈出一個Activity,因爲安卓系統自身的設計,咱們只能在手機屏幕上獲取當前一個Activity的焦點即棧頂元素(最上面的Activity),其他的Activity會暫居後臺等待系統調用。code

 

1.一、關於任務棧的概念:xml

任務棧是用來提高體驗 而設計的:
(1) 程序打開時就建立了一個任務棧, 用於存儲當前程序的activity,當前程序(包括被當前程序所調用的)全部的activity屬於一個任務棧。
(2) 一個任務棧包含了一個activity的集合, 能夠有序的選擇哪個activity和用戶進行交互,只有在任務棧棧頂的activity才能夠跟用戶進行交互。
(3) 任務棧能夠移動到後臺,而且保留了每個activity的狀態. 而且有序的給用戶列出它們的任務, 並且還不丟失它們狀態信息。
(4) 退出應用程序時,當把全部的任務棧中全部的activity清除出棧時,任務棧會被銷燬,程序退出。

1.二、關於任務棧的缺點:

(1) 每開啓一次頁面都會在任務棧中添加一個Activity,而只有任務棧中的Activity所有清除出棧時,任務棧被銷燬,程序纔會退出,這樣就形成了用戶體驗差,須要點擊屢次返回才能夠把程序退出了。
(2) 每開啓一次頁面都會在任務棧中添加一個Activity還會形成數據冗餘 重複數據太多,會致使內存溢出的問題(OOM)。

 

二、Activity的4種啓動方式

  爲了解決任務棧產生的問題,Android爲Activity設計了啓動模式,那麼下面的內容將介紹Android中Activity的啓動模式,這也是最重要的內容之一。

  啓動模式(launchMode)在多個Activity跳轉的過程當中扮演着重要的角色,它能夠解決是否生成新的Activity實例,是否重用已經存在的Activity實例,是否和其餘實例共用一個任務棧。任務棧是一個具備棧結構的對象,一個任務棧能夠管理多個Activity,每啓動一個應用,也就建立一個與之對應的任務棧。

  Activity一共有如下四種launchMode模式:一、standard 二、singTop 三、singTask 四、singleInstance,咱們能夠在AndroidManifest.xml配置 <activity>的android:launchMode屬性爲以上四種之一便可。
2.一、實踐是檢驗真理的惟一標準
 下面寫個實例,有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         }

一、在standard(默認)啓動模式下,咱們來回的去跳轉Activity,看下日誌打印,發現是不會調用onNewIntent方法的,由於它不是一個實例。

二、在singleTop模式下,咱們從第一個Activity跳轉到第二個Activity,再從第二個Activity跳轉自身,再跳轉第一個Activity,看下日誌打印,咱們能夠發現,當第二個Activity置於棧頂的時候,因爲重用了實例,因此調用了onNewIntent方法。

三、當singleTask和singleInstance模式下也是同樣的,由於重用了實例,因此會調用onNewIntent方法,且onNewIntent方法是在前一個Activity的onStop方法後(當前ActivityonReStart方法前)當即調用的。

相關文章
相關標籤/搜索