Android中Activity的啟動模式(LaunchMode)和使用場景

weixin_34321977發表於2017-09-23

轉載請註明出處:http://www.jianshu.com/p/c4cba9c94fa8
本文出自Shawpoo的簡書
我的部落格:CSDN部落格

一、為什麼需要啟動模式

在Android開發中,我們都知道,在預設的情況下,如果我們啟動的是同一個Activity的話,系統會建立多個例項並把它們一一放入任務棧中。當我們點選返回(back)鍵,這些Activity例項又將從任務棧中一一移除,遵循的原則是“後進先出”(先進後出)。

這裡我們考慮一個問題,當我們多次啟動同一個Activity,系統也會建立多個例項放入任務棧中,這樣豈不是很耗費記憶體資源?為了解決這一問題,Android為Actiivty提供了啟動模式。

Activity的啟動模式有四種:standard、singleTop、singleTask和singleInstance

二、啟動模式的分類

1、standard:標準模式

這種啟動模式為標準模式,也是預設模式。每當我們啟動一個Activity,系統就會相應的建立一個例項,不管這個例項是否已經存在。這種模式,一個棧中可以有多個例項,每個例項也都有自己的任務棧。而且是誰啟動了此Activity,那麼這個Activity就執行在啟動它的Activity所在的棧中。

  • Manifest中配置:
    對於標準模式,android:launchMode="standard"可以不寫,因為預設就是standard模式。
<activity
     android:name=".StandardActivity"
     android:launchMode="standard" >
</activity>
  • 使用案例:

MainActivity有一個按鈕,點選按鈕會開啟StandardActivity。開啟StandardActivity也有一個按鈕,點選也是啟動一個StandardActivity。並且我們在onCreate()方法中列印TaskId和hashCode值。
開啟步驟:MainActivity->StandardActivity->StandardActivity->StandardActivity

MainActivity:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_demo);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StandardActivity.open(MainActivity.this);
            }
        });

        Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());

    }
    
}

StandardActivity :

/**
 * 啟動模式:Standard(標準模式)
 */

public class StandardActivity extends AppCompatActivity {


    private static final String TAG = StandardActivity.class.getSimpleName();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_launch_mode);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                open(StandardActivity.this);
            }
        });

        Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
    }

    public static void open(Context context) {
        context.startActivity(new Intent(context, StandardActivity.class));
    }

}

控制檯列印log如下:

3262125-1631b52b2e35f9df.png

通過案例的log分析,可以得出標準模式下,每當開啟一次Activity就會建立一個新的例項,因為hashCode值都不同,而且都建立在啟動它的Activity所屬的任務棧中,也就是MainActivity所在的任務棧中,因為它們的任務棧Id一致。

  • 分析總結:

標準模式下,只要啟動一次Activity,系統就會在當前任務棧新建一個例項。

  • 使用場景:

正常的去開啟一個新的頁面,這種啟動模式使用最多,最普通 。

2、singleTop:棧頂複用模式

這種啟動模式下,如果要啟動的Activity已經處於棧的頂部,那麼此時系統不會建立新的例項,而是直接開啟此頁面,同時它的onNewIntent()方法會被執行,我們可以通過Intent進行傳值,而且它的onCreate(),onStart()方法不會被呼叫,因為它並沒有發生任何變化。

  • Manifest中配置:
 <activity
       android:name=".SingleTopActivity"
       android:launchMode="singleTop">
</activity>
  • 使用案例:

MainActivity仍然是一個按鈕,點選按鈕開啟SingleTopActivity,SingleTopActivity有兩個按鈕,一個是開啟SingleTopActivity,一個是開啟OtherActivity,OtherActivity有一個按鈕,點選按鈕可以開啟SingleTopActivity。而且我們在onCreate()、onNewIntent()列印taskId和hashCode值。

開啟步驟:MainActivity->SingleTopActivity->SingleTopActivity->OtherActivity->SingleTopActivity->SingleTopActivity

MainActivity:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_demo);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SingleTopActivity.open(MainActivity.this);
            }
        });

        Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());

}

SingleTopActivity :

/**
 * 啟動模式:棧頂複用模式
 */

public class SingleTopActivity extends AppCompatActivity {

    private static final String TAG = SingleTopActivity.class.getSimpleName();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_launch_mode);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                open(SingleTopActivity.this);
            }
        });
        findViewById(R.id.btn_other).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                OtherActivity.open(SingleTopActivity.this);
            }
        });

        Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.e(TAG, "———onNewIntent(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
    }

    public static void open(Context context) {
        context.startActivity(new Intent(context, SingleTopActivity.class));
    }

}

OtherActivity:

public class OtherActivity extends AppCompatActivity {

    private static final String TAG = OtherActivity.class.getSimpleName();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

        findViewById(R.id.btn_singleTop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SingleTopActivity.open(OtherActivity.this);
            }
        });

        Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
    }

    public static void open(Context context) {
        context.startActivity(new Intent(context, OtherActivity.class));
    }

}

控制檯列印log如下:

3262125-c056451679761a09.png

首先,因為它們的taskId值都相同,所以它們同屬於一個任務棧。其次,第一次啟動SingleTopActivity的時候會執行onCreate()方法新建一個例項,然後再次啟動SingleTopActivity頁面會回撥onNewIntent(),說明沒有建立新的例項,而且hashCode值沒有發生改變。此時我們繼續開啟另一個Activity,這時OtherActivity處於棧頂,我們繼續啟動SingleTopActivity,這時發現又是執行了onCreate(),說明又重新建立了新的例項,當我們繼續啟動SingleTopActivity,發現回撥了onNewIntent(),同樣hashCode值沒有發生改變,證明沒有重新建立例項。

  • 分析總結:

通過上述案例歸納為以下三點:
1、當前棧中已有該Activity的例項並且該例項位於棧頂時,不會建立例項,而是複用棧頂的例項,並且會將Intent物件傳入,回撥onNewInten()方法;
2、當前棧中已有該Activity的例項但是該例項不在棧頂時,其行為和standard啟動模式一樣,依然會建立一個新的例項;
3、當前棧中不存在該Activity的例項時,其行為同standard啟動模式。

  • 使用場景:

這種模式應用場景的話,假如一個新聞客戶端,在通知欄收到了3條推送,點選每一條推送會開啟新聞的詳情頁,如果為預設的啟動模式的話,點選一次開啟一個頁面,會開啟三個詳情頁,這肯定是不合理的。如果啟動模式設定為singleTop,當點選第一條推送後,新聞詳情頁已經處於棧頂,當我們第二條和第三條推送的時候,只需要通過Intent傳入相應的內容即可,並不會重新開啟新的頁面,這樣就可以避免重複開啟頁面了。

3、singleTask:站內複用模式

在這個模式下,如果棧中存在這個Activity的例項就會複用這個Activity,不管它是否位於棧頂,複用時,會將它上面的Activity全部出棧,因為singleTask本身自帶clearTop這種功能。並且會回撥該例項的onNewIntent()方法。其實這個過程還存在一個任務棧的匹配,因為這個模式啟動時,會在自己需要的任務棧中尋找例項,這個任務棧就是通過taskAffinity屬性指定。如果這個任務棧不存在,則會建立這個任務棧。不設定taskAffinity屬性的話,預設為應用的包名。

  • 使用案例:

MainActivity仍然是一個按鈕,點選按鈕開啟SingleTaskActivity,SingleTaskActivity有兩個按鈕,一個是開啟SingleTaskActivity,另一個同樣是開啟OtherActivity,OtherActivity有一個按鈕,點選按鈕可以開啟SingleTaskActivity。同樣我們在onCreate()、onNewIntent()列印taskId和hashCode值。

開啟步驟:MainActivity->SingleTaskActivity->SingleTaskActivity->OtherActivity->SingleTaskActivity->SingleTaskActivity

程式碼和SingleTop基本一樣,只有Manifest中配置不同,這裡不再贅述。

1、不設定taskAffinity屬性,也就是默在同一個任務棧中。

Manifest中配置:

<activity
     android:name=".SingleTaskActivity"
     android:launchMode="singleTask">
</activity>

控制檯列印log如下:

3262125-ce9b75c81bd80c61.png

首先,因為發現它們的taskId值都相同,所以它們同屬於一個任務棧。其次,第一次啟動SingleTaskActivity的時候會執行onCreate()方法新建一個例項,然後再次啟動SingleTaskActivity頁面會回撥onNewIntent(),說明沒有建立新的例項,而且hashCode值沒有發生改變。此時我們繼續開啟另一個Activity,然後繼續啟動SingleTaskActivity,這時發現仍然只回撥onNewIntent(),說明沒有建立新的例項,當我們繼續啟動SingleTaskActivity,仍然只是回撥了onNewIntent(),此過程中發現hashCode值始終沒有發生改變,證明引用都是同一個的例項。

2、設定taskAffinity屬性,singleTask所在的Activity與啟動它的Activity處於不同的任務棧中。

<activity
     android:name=".SingleTaskActivity"
     android:launchMode="singleTask"
     android:taskAffinity="${applicationId}.singleTask">
</activity>
3262125-8bdf953f17ff5f1c.png

指定了taskAffinity後,我們發現除了taskId有區別外,其他呼叫基本沒有什麼區別。因為MainActivity沒有指定taskAffinity屬性,預設為包名,與SingleTaskActivity不同,所以在啟動SingleTaskActivity時,發現這個棧不存在,系統首先會建立這個棧然後將SingleTaskActivity壓入棧中。之後我們發現只要棧中存在SingleTaskActivity這個例項,就會直接引用。

3、通過adb shell dumpsys activity activities檢視當前執行的Activity

3262125-b8a2d16436b7fff4.png

執行完上面的步驟,我們通過上面的資訊得出,發現只有MainActivity和SingleTaskActivity在執行,而且也可以看出確實有1909和1910兩個任務棧。那OtherActivity哪去了?那是因為SingleTaskActivity具有ClearTop的功能,當複用SingleTashActivity的時候會將棧中SingleTaskActivity之上的Activity全部清掉,所以OtherActivity其實是被銷燬了。

  • 分析總結:

在複用的時候,首先會根據taskAffinity去找對應的任務棧:
1、如果不存在指定的任務棧,系統會新建對應的任務棧,並新建Activity例項壓入棧中。
2、如果存在指定的任務棧,則會查詢該任務棧中是否存在該Activity例項
a、如果不存在該例項,則會在該任務棧中新建Activity例項。
b、如果存在該例項,則會直接引用,並且回撥該例項的onNewIntent()方法。並且任務棧中該例項之上的Activity會被全部銷燬。

  • 使用場景:

SingleTask這種啟動模式最常使用的就是一個APP的首頁,因為一般為一個APP的第一個頁面,且長時間保留在棧中,所以最適合設定singleTask啟動模式來複用。

4、singleInstance:單例項模式

單例項模式,顧名思義,只有一個例項。該模式具備singleTask模式的所有特性外,與它的區別就是,這種模式下的Activity會單獨佔用一個Task棧,具有全域性唯一性,即整個系統中就這麼一個例項,由於棧內複用的特性,後續的請求均不會建立新的Activity例項,除非這個特殊的任務棧被銷燬了。以singleInstance模式啟動的Activity在整個系統中是單例的,如果在啟動這樣的Activiyt時,已經存在了一個例項,那麼會把它所在的任務排程到前臺,重用這個例項。

  • Manifest中配置:
<activity
    android:name=".SingleInstanceActivity"
    android:launchMode="singleInstance">
</activity>
  • 使用案例:

使用上面同樣的程式碼進行測試:

3262125-75274a360861b3bb.png

通過測試發現,在第一次開啟SingleInstanceActivity的時候,由於系統不存在該例項,所以系統會新建一個任務棧來存放該Activity例項,而且只要開啟過一次該Activity,後面無論什麼時候再次啟動該Activity,都會直接引用第一次建立的例項,而且會回撥該例項的onNewIntent()方法。

  • 分析總結:

啟動該模式Activity的時候,會查詢系統中是否存在:
1、不存在,首先會新建一個任務棧,其次建立該Activity例項。
2、存在,則會直接引用該例項,並且回撥onNewIntent()方法。
特殊情況:該任務棧或該例項被銷燬,系統會重新建立。

  • 使用場景:

很常見的是,電話撥號盤頁面,通過自己的應用或者其他應用開啟撥打電話頁面 ,只要系統的棧中存在該例項,那麼就會直接呼叫。

三、總結

在使用APP過程中,不可避免頁面之間的跳轉,那麼就會涉及到啟動模式。其實在對介面進行跳轉時,Android系統既能在同一個任務中對Activity進行排程,也能以Task(任務棧)為單位進行整體排程。在啟動模式為standard或singleTop時,一般是在同一個任務中對Activity進行排程,而在啟動模式為singleTask或singleInstance是,一般會對Task進行整體排程。

相關文章