天天看點

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=" ">屬性中指定。

Android安卓 Activity四種啟動模式(launchMode) standard, singleTop, singleTask, singleInstance啟動模式的重要性安卓中的啟動模式

安卓使用 傳回棧(Back Stack) 來管理活動。不同的啟動模式,實際上對應的是傳回棧中不同的變化。(而調用finish()方法銷毀一個活動的時候,對應的都是棧頂活動的出棧)

1 standard

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

standard模式的特點:

  • 當啟動一個Activity時,如果這個Activity的啟動模式為預設的standard模式,那麼無論目前棧頂元素是否是它本身,他都會簡單地建立一個Activity,并将其加到棧頂。

因為standard模式比較簡單,是以這裡直接通過代碼來示範一下standard啟動模式時,頁面、傳回棧中内容的變化。

  1. 建立一個LaunchModeTest 工程,并在其中建立FirstActivity和其對應的xml。如圖所示。由于沒有指定其啟動模式,是以會采用預設的standard啟動模式。
    Android安卓 Activity四種啟動模式(launchMode) standard, singleTop, singleTask, singleInstance啟動模式的重要性安卓中的啟動模式
  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. 運作程式,觀察運作效果。
Android安卓 Activity四種啟動模式(launchMode) standard, singleTop, singleTask, singleInstance啟動模式的重要性安卓中的啟動模式

可以看到,每次都可以啟動一個新的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. 運作程式,觀察效果。
Android安卓 Activity四種啟動模式(launchMode) standard, singleTop, singleTask, singleInstance啟動模式的重要性安卓中的啟動模式

和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. 運作,觀察結果
Android安卓 Activity四種啟動模式(launchMode) standard, singleTop, singleTask, singleInstance啟動模式的重要性安卓中的啟動模式

在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很好的情況,是以就先不寫執行個體了,等有時間再來補充一下。