Android安卓 Activity四種啓動模式(launchMode) standard, singleTop, singleTask, singleInstance

啓動模式的重要性

Android編程中經常涉及到頁面的切換,啓動一個新的頁面(或者說Activity)的時候需要爲其指定合適的“啓動模式”。指定的啓動模式不合適,會出現類似下面這種奇怪的效果:

  • 你拿起QQ切換了一個新的賬號,一直按返回卻沒有退出程序,而是又回到了舊帳號對應的頁面…
  • 你點擊別人頭像的時候不知道爲什麼系統卡頓了,於是你又點擊了幾次,等到系統反應過來,給你打開了一個又一個別人的主頁,你只好一個又一個地退出,因爲這些頁面實際上是一樣的…

這些情況都是我在自己寫項目或者使用市場上一些軟件的時候遇到的。爲了實現Activity切換、顯示等的正常,我們需要爲活動指定適合的啓動模式(launchMode)

安卓中的啓動模式

Android編程時Activity有4種啓動模式,分別爲standard,singleTop,singTask和singleInstance。使用Android Studio開發時每新建一個活動,Android Studio都會爲我們在AndroidManifest.xml中爲其註冊。活動的啓動模式在標籤中的<android: launchMode=" ">屬性中指定。
在這裏插入圖片描述
安卓使用 返回棧(Back Stack) 來管理活動。不同的啓動模式,實際上對應的是返回棧中不同的變化。(而調用finish()方法銷燬一個活動的時候,對應的都是棧頂活動的出棧)

1 standard

standard是活動的默認啓動模式。如果不使用launchMode屬性來指定,系統就會默認這種啓動模式。

standard模式的特點:

  • 當啓動一個Activity時,如果這個Activity的啓動模式爲默認的standard模式,那麼無論當前棧頂元素是否是它本身,他都會簡單地新建一個Activity,並將其加到棧頂

因爲standard模式比較簡單,所以這裏直接通過代碼來演示一下standard啓動模式時,頁面、返回棧中內容的變化。

  1. 新建一個LaunchModeTest 工程,並在其中創建FirstActivity和其對應的xml。如圖所示。由於沒有指定其啓動模式,所以會採用默認的standard啓動模式。
    在這裏插入圖片描述
  2. 在activity_first.xml中添加一個加入如下代碼,使其頁面上出現一個寫着"To myself"的Button。我們下面將實現點擊這個Button,來從“自己”跳到“自己”。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="To myself"
        />

</LinearLayout>
  1. 在FirstActivity中對按鈕添加監聽事件——由FirstActivity切換到FirstActivity。
public class FirstActivity extends AppCompatActivity {

    private Button button1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        button1 = findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}
  1. 運行程序,觀察運行效果。

在這裏插入圖片描述
可以看到,每次都可以啓動一個新的FirstActivity,退出時要一個一個退回。其中的棧結構變化爲:

  • 啓動軟件:FirstActivtiy
  • 點擊button一次:Firstctivity->FirstActivtiy
  • 點擊button兩次:Firstctivity->FirstActivtiy->FirstActivity
  • 返回一次:Firstctivity->FirstActivtiy
  • 返回兩次:Firstctivity
  • 返回三次:(退出程序了)

2 singleTop

singleTop其實可以從字面上理解爲棧頂Activity的唯一性。其特點在於:

singleTop模式下,假如新建的Activity和棧頂的Activity是同一個Activity,那麼就不會再新建。

實例如下:

  1. 在上述代碼的基礎上,只做一個修改:在AndroidManifest.xml中的標籤中指定launchMode爲singleTop。
        <activity android:name=".FirstActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
  1. 運行程序,觀察效果。

在這裏插入圖片描述
和standard模式同樣的代碼,只是修改了啓動模式,點擊按鈕之後不再會創建多餘的自己了。這樣即使系統卡頓、網絡延遲,也可以消除堆疊多個同樣Activity的情況。
其中的棧結構變化爲:

  • 啓動軟件:FirstActivtiy
  • 點擊button一次:Firstctivity
  • 點擊button兩次:Firstctivity
  • 返回一次:(退出程序了)

3 singleTask

singleTask其實也可以顧名思義,不就是一個返回棧中某個Activity只有一個實例嗎?如何做到只有一個實例呢?
假設現在棧底到棧頂的情況依次爲:A->B->C->D 現在D要啓動launchMode爲singleTask的B,那麼棧中情況如何變化才能保證只有一個B呢?難道將B單獨抽出、提到棧頂,直接變成A->C->D->B嗎?這顯然不太符合邏輯。倒不如是直接將C、D出棧,直接讓B變成棧頂元素,變成A->B,這樣還可以少創建一次了呢。
實際上,安卓就是這樣處理的——

當啓動一個launchMode爲singleTask的Activity時,假設之前沒有創建過就需要重新創建;有,就直接使用原來的那個,並且將其上所有Activity全部出棧。

大家可能會注意到,我上面說了“使用原來的那個”,這意味着,如果系統已經創建過一次singleTask的B,並且還沒有將其銷燬,那麼D啓動B時,實際上不是新創建了一個B,而是直接使用的原來的B。如果我們重寫B的onCreate()和onStart()方法,可以發現B第一次被創建時會調用的是onCreate()方法,而被D啓動時會調用onStart()方法,說明其只是由不可見變爲可見,而不是重新創建。

這裏可以使用一個小例子來直觀地說明singleTask直接複用以前創建的Activity這個特點:

  1. 創建一個project,並且在其中創建LoginActivity、AfterLoginActivity1、AfterLoginActivity2三個Activity和其對應的xml文件。指定LoginActivity的launchMode爲singleTask。其中用LoginActivity的xml模擬登錄界面,而另外兩個都只放一個button,用於實現跳轉到另一個界面。

AndroidManifest.xml相關代碼:

<activity
            android:name=".LoginActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

activity_login.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="10dp"
    tools:context=".LoginActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="賬號:"
            android:textSize="20sp"
            android:layout_gravity="center_vertical"/>
        <EditText
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="密碼:"
            android:textSize="20sp"
            android:layout_gravity="center_vertical"/>

        <EditText
            android:id="@+id/editText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword" />

    </LinearLayout>

    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="login"
        />

</LinearLayout>

其他部分代碼非常簡單,就不貼了。整個程序的邏輯是從LoginActivity可以跳轉到AfterLoginActivity1,從AfterLoginActivity2可以跳轉到LoginActivity。

  1. 運行,觀察結果

在這裏插入圖片描述
在AfterLoginActivity2中點擊“SWITCH ACCOUNT”之後,程序切到了LoginActivity,但是我們可以發現,之前輸入的姓名和密碼都還存在,可見,這其實是直接使用的以前創建的LoginActivity。再使用返回鍵,程序直接退回到了桌面,更是說明這的確是初始是已經存在的那個LoginActivity,且其他兩個Activity都已經出棧了,而不是將LoginActivity提到了棧頂。

其棧結構變化爲:

  • 程序啓動:LoginActivity
  • 點擊跳轉到After1:LoginActivity->AfterLoginActivity1
  • 點擊跳轉到After2:LoginActivity->AfterLoginActivity1->LoginActivity2
  • 點擊跳轉到LoginActivity:LoginActivity
  • 點擊返回:(退出程序)

4 singleInstance

singleInstance,名稱是單例模式,也即,系統中只有那麼一個實例。既然只有一個,那麼也就說明很重要、很特殊咯,我們需要將其“保護起來”。安卓對單例模式的“保護措施”是將其單獨放到一個任務棧中
假設現在有A、B、C、D四個Activity,其中B爲singleInstance模式,而其他是standard模式。A啓動B,B啓動C,C啓動D,那麼此時棧中的情況如何呢?我們說了,B需要單獨放入一個棧中,所以這個時候會存在兩個棧,一箇中的內容爲A->C->D,一個爲B。現在我們已經在D活動了,依次按返回鍵銷燬這些活動,那麼C是否會返回B呢?答案是否定的,因爲B和C不在一個棧中,一次會C無法返回B,而是直接返回A。從A返回會並不會直接退出程序,而是出現B。因爲此時存在兩個任務棧,第一個棧中的A、C、D均已經被銷燬,系統就找到了另一個棧中的B,將B也銷燬,纔會完全退出程序。

不過我思考了很久,仍然還是沒有想到什麼使用singleInstacne很好的情況,所以就先不寫實例了,等有時間再來補充一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章