1. 程式人生 > >帶你深入理解Activity啟動模式(LaunchMode)

帶你深入理解Activity啟動模式(LaunchMode)

我們知道預設情況下,當我們多次啟動同一個activity時,系統會建立多個例項並把他們一個個放入任務棧,當我們按back鍵,這些activity又會一個個退出。在講activity的launchmode之前,我們有必要了解下“任務棧(Task Stack)”這個概念。在Android中是使用任務(Task)來管理Activity的,任務就是存放在棧裡面的Activity的集合,這個棧就是稱為任務棧。

一,Activity的LaunchMode

明白任務棧的概念後,我們再來了解Activity的啟動模式。目前Activity有四種啟動模式:standard、singleTop、singleTask、singleInstance。下面我們一一介紹:

  • standard:標準模式,這是系統預設的預設,也就是說你不設定Activity的launchMode時,預設的就是standard。在這種模式下,每次啟動一個Activity都會重新建立一個新的例項,不管這個例項是否存在。在這種模式下,誰啟動了這個Activity,那麼這個Activity就執行在啟動它的那個Activity所在的棧,但是這是有條件的,前提是啟動它的Activity不能是singleInstance,因為singleInstance只能獨立於一個任務棧中,不能有其他的Activity例項,這個後面我們會驗證。比如非singleInstance模式的Activity A啟動了標準模式的Activity B,那麼Activity B就會進入到Activity A所在的任務棧。
  • singleTop:棧頂複用模式。在這種模式下,如果Activity已經在任務棧的棧頂了,當再次啟動同一個Activity的時候,這個Activity不會被重新建立,而且它的onNewIntent()方法會被呼叫,但是它的onCreate()、onStart()方法不會被呼叫。此模式下的Activity也會進入啟動它的非singleInstance模式的Activity所在的任務棧中。
  • singleTask:棧內複用模式。在這種模式下,只要Activity存在棧內,那麼多次啟動這個Activity都不會重新建立例項,系統會呼叫它的onNewIntent()方法。此外有個需要注意的地方:singleTask有clear top的效果,也就是說會將其以上的Activity全部出棧。
  • singleInstance:這是singleTask的一種加強模式,除了singleTask所有特性以外,具有此模式的Activity只能單獨位於一個任務棧中。

二、例項講解

介紹完四種啟動模式後,我們看下各個模式的具體的例子。先介紹下背景:我們定義四個Activity,launchMode分別為:standard、singleTop、singleTask、singleInstance。對應的Activity的名稱分別為:ActivityA、ActivityB、ActivityC、ActivityD。然後我們還定義了其各自的taskAffinity,這個引數標示了該Activity所需要的任務棧的名字,預設情況下,所有的Activity所需的任務棧的名字為應用的包名。先看程式碼,AndroidManifest定義如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jun.zhuzp.myapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".launchmode.ActivityA"
            android:taskAffinity="com.jun.zhuzp.a1"
            android:launchMode="standard"/>
        <activity android:name=".launchmode.ActivityB"
            android:taskAffinity="com.jun.zhuzp.a2"
            android:launchMode="singleTop"/>
        <activity android:name=".launchmode.ActivityC"
            android:taskAffinity="com.jun.zhuzp.a3"
            android:launchMode="singleTask"/>
        <activity android:name=".launchmode.ActivityD"
            android:taskAffinity="com.jun.zhuzp.a4"
            android:launchMode="singleInstance"/>
    </application>

</manifest>

MainActivity是測試應用的主Activity,每個Activity中都有一個button,點選button就會去啟動相應的Activity。我們在測試的過程中是這樣的:MainActivity啟動ActivityA,ActivityA啟動ActivityB,ActivityB啟動ActivityC,ActivityC啟動ActivityD,然後ActivityD啟動ActivityA,這樣一個可以迴圈的過程。我們先看ActivityA的程式碼:

public class ActivityA  extends Activity{
    private static final String TAG = "ActivityA";
    private Button mSwitchButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate...");
        setContentView(R.layout.activity_launchmode);
        Log.d(TAG,"Task id = " + getTaskId());
        iniView();
    }

    private void iniView() {
        mSwitchButton = (Button)findViewById(R.id.btn_switch2);
        mSwitchButton.setText("啟動ActivityB");
        mSwitchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClassName("com.jun.zhuzp.myapplication","com.jun.zhuzp.myapplication.launchmode.ActivityB");
                startActivity(intent);
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"onStart...");
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d(TAG,"onNewIntent...");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume...");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy...");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop...");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause...");
    }
}

程式碼內容很簡單,onCreate()方法中的getTaskId()用於列印該Activity所處的任務棧,然後就是對button設定了啟動ActivityB的點選事件。其他幾個Activity 的程式碼基本一樣,只是button的點選事件不一樣而已,這裡就不貼程式碼了。接下來,我們來一步一步的分析我們寫的測試效果。

  • standard模式的MainActivity啟動ActivityA(說明:我在MainActivity的onCreate()方法中也加了getTaskId()的列印),我們先看下log
    這裡寫圖片描述
    我們我們可以看到,兩個Activity的Task id 是一樣的,這就是說明,ActivityA進入了MainActivity所在的棧,然後我們再看下adb shell dumpsys activity 命令下的的結果:
    這裡寫圖片描述
    我們可以看到Task id為89的任務棧的名稱com.jun.zhuzp.myapplication。這是測試應用的包名,也是MainActivity任務棧名。但是我們在AndroidManifest檔案中命名定義了ActivityA 的taskAffinity屬性啊,它所在的任務棧仍然是MainActivity所在的任務棧,這是因為我們前面提到的:非singleInstance模式的Activity啟動standard模式的Activity,被啟動的Activity會進入啟動它的那個Activity所在的任務棧中。那什麼情況下,taskAffinity定義的任務棧才會起作用呢,想必大家也已經猜到了,就是singleInstance模式的Activity啟動它時,才會建立一個名為taskAffinity所指定新的任務棧,這個結論會在singleInstance模式的ActivityD中啟動standard ActivityA得到驗證。

  • standard模式ActivityA啟動singleTop模式的ActivityB。同樣我們看log和adb shell dumpsys activity命令下的結果:
    這裡寫圖片描述
    這裡寫圖片描述
    和ActivityA類似,雖然為ActivityB設定了taskAffinity,但是ActivityB所在的任務棧仍然是MainActivity所在的任務棧,原因和前面standard模式一樣。非singleInstance模式的Activity啟動singleTop模糊的Activity,被啟動的Activity會進入啟動它的那個Activity所在的任務棧中,所以ActivityB和ActivityA在相同的任務棧中。

  • singleTop模式的ActivityB啟動指定了taskAffinity的singleTask模式的ActivityC,照樣先看logo和adb shell dumpsys activity結果:
    這裡寫圖片描述
    這裡寫圖片描述
    這個時候taskAffinity設定的任務棧名終於起作用了,這是為什麼呢?因為當啟動指定了任務棧的ActivityC時,系統會去判斷ActivityC所需的任務棧名是否存在,如果不存在,就會建立其所需的任務棧,並將ActivityC壓入建立的任務棧,這也就是上面所呈現的結果。那如果我們在AndroidManifest中沒有設定ActivityC的android:taskAffinity屬性時,那個ActivityC就是和ActivityA、ActivityB、MainActivity在同一個任務棧中。

  • singleTask模式的ActivityC啟動singleInstance模式的ActivityD
    這裡寫圖片描述
    這裡寫圖片描述
    我們說過,singleInstance模式是singleTask的加強版,除了singleTask具有的特性外(這一點就在從logo和dumpsys activity結果可以看出),此種模式的Activity只能獨立於一個任務棧中,這一點後面我們會驗證。另外一點,如過沒有指定singleInstance的任務棧名會怎樣呢,系統還是會為它建立一個新的任務棧,任務棧名就是應用的包名,但是這並不表示沒有指定任務棧名的ActivityD和MainActivity就在同一個任務棧中,雖然他們的任務棧名都是預設的應用包名,他們的Task id 是不一樣的!!!

  • singleInstance模式ActivityD啟動設定了taskAffinity屬性的standard模式ActivityA
    這裡寫圖片描述
    這裡寫圖片描述
    這個時候,我們看到系統建立了一個新的任務棧——com.jun,zhuzp.a1。這就是我們為standard模式的ActivityA定義的taskAffinity指定的任務棧名,它終於起作用了!!!但是要銘記,它起作用的前提是singleInstance模式的Activity啟動它,這是因為singleInstance模式任務棧中只能有一個Activity例項,standard模式的Activity想進也進不去啊,沒辦法系統只能再建立一個新的任務棧了,然後看到:額,還要個名字,那就給你命名吧。

  • 最後一點,我們來驗證下singleTask模式的Activity具有clear top的功能,AndroidManifest檔案,我們不變,我們改下啟動Activity的方式:MainActivity—>ActivityC—>ActivityA—>ActivityB—>ActivityC。
    這是MainActivity—>ActivityC—>ActivityA—>ActivityB過程的任務棧情況,這個時候Task id為97的棧中有三個Activity,從棧頂到棧底依次是:ActivityB、ActivityA、ActivityC。singleTask模式的ActivityC在棧底。
    這裡寫圖片描述
    然後我們再執行ActivityB—>ActivityC時,任務棧的情況:Task id 為97的棧中只有ActivityC裡,另外兩個都已經出棧了,這就是singleTask具有的clear top 的功能。同時,這個時候也不會走Activity的onCreate()方法了,會走onNewIntent()方法。
    這裡寫圖片描述

補充:我們也可以在程式碼中通過給Intent來設定標誌位來為Activity指定啟動模式,例如:

Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setClassName("com.jun.zhuzp.myapplication","com.jun.zhuzp.myapplication.launchmode.ActivityA");
startActivity(i);

但是這種方式還是和在AndroidManifest中設定launchMode有差別的,首先設定Activity的flag的優先順序比在AndroidManifest中定義的優先順序高;其次他們也有各自的使用範疇,比如AndroidManifest中只能定義四中:standard、singleTop、singleTask、singleInstance;flag就是很多種,具體大家可以看下文件。
這裡我們列舉部分Flag講解
FLAGE_ACTIVITY_NEW_TASK,這個標誌位對應於AndroidManifest中設定的“singleTask”
FLAGE_ACTIVITY_SINGLE_TOP,這個標誌位對應於AndroidManifest中設定的“singleTop”
FLAGE_ACTIVITY_CLEAR_TOP,當Activity設定此標誌位後,啟動它時,在同一任務棧中所有位於它之上的Activity都會出棧。比如棧自底向上依次是:MainActivity—ActivityA—ActivityB,各個Activity 的launchMode還是給出的AndroidManifest中的定義,這個時候如果再啟動ActivityA,同時設定了flag為FLAGE_ACTIVITY_CLEAR_TOP。那麼棧內的自底向上就成了這樣:MainActivity—ActivityA(此ActivityA例項不是之前棧內的那個,而是新的,之前的已出棧)。

三,總結

上面就是有關Activity啟動模式的主要內容了,最後,我們通過一系列的圖來總結下,方便大家理解。
1,standard,標準模式,每次啟動都會建立新的例項,不管例項是否存在
這裡寫圖片描述
2,singleTop,棧頂複用,如果Activity處於棧頂,則不會建立新的Activity
這裡寫圖片描述
3,singleTask,棧內複用,只要Activity在棧記憶體在,那麼多次啟動該Activity都不會建立新的Activity例項。預設具有clear top的效果。
這裡寫圖片描述
4,singleInstance,在singleTask的基礎上,限制了此模式的Activity只能單獨地位於一個任務棧中。
這裡寫圖片描述