Activity-任務棧和啟動模式


 為什么需要了解關於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方法前)立即調用的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM