Android溫故而知新 - launchMode

Activity有四種launchMode,android官方文檔的介紹如下:

啟動模式 多個(gè)實(shí)例? 注釋
“standard” 默認(rèn)值。系統(tǒng)始終會在目標(biāo)任務(wù)中創(chuàng)建新的 Activity 實(shí)例并向其傳送 Intent。
“singleTop” 有條件 如果目標(biāo)任務(wù)的頂部已存在一個(gè) Activity 實(shí)例,則系統(tǒng)會通過調(diào)用該實(shí)例的 onNewIntent() 方法向其傳送 Intent,而不是創(chuàng)建新的 Activity 實(shí)例。
“singleTask” 系統(tǒng)在新任務(wù)的根位置創(chuàng)建 Activity 并向其傳送 Intent。 不過,如果已存在一個(gè) Activity 實(shí)例,則系統(tǒng)會通過調(diào)用該實(shí)例的 onNewIntent() 方法向其傳送 Intent,而不是創(chuàng)建新的 Activity 實(shí)例。
“singleInstance” 與“singleTask"”相同,只是系統(tǒng)不會將任何其他 Activity 啟動到包含實(shí)例的任務(wù)中。 該 Activity 始終是其任務(wù)唯一僅有的成員。

測試demo

我們通過兩個(gè)Activity之間的跳轉(zhuǎn)和任務(wù)棧打印來理解launchMode的作用。

這兩個(gè)Activity使用同一個(gè)布局, TextView用來打印任務(wù)棧的id和activity實(shí)例,然后是兩個(gè)按鈕,分別用來啟動兩個(gè)activity:

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

    <TextView
        android:id="@+id/label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/gotoFirstActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="goto first activity" />

    <Button
        android:id="@+id/gotoSecondActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="goto second activity" />

</LinearLayout>

Activty代碼如下:

public class FirstActivity extends AppCompatActivity {

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

        Utils.initActivity(this);
    }

    public void onClick(View view) {
        Utils.onClick(this, view.getId());
    }
}
public class SecondActivity extends Activity {

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

        Utils.initActivity(this);
    }

    public void onClick(View view) {
        Utils.onClick(this, view.getId());
    }
}

因?yàn)閮蓚€(gè)activity的代碼很相似,所以我把它們放到了靜態(tài)類Utils中:

public class Utils {
    public static void initActivity(Activity activity) {
        TextView label = (TextView) activity.findViewById(R.id.label);
        label.setText("task : " + activity.getTaskId() + " activity : " + activity);
    }


    public static void onClick(Context context, int id) {
        Intent intent;
        if (id == R.id.gotoFirstActivity) {
            intent = new Intent(context, FirstActivity.class);
        } else {
            intent = new Intent(context, SecondActivity.class);
        }
        context.startActivity(intent);
    }
}

查看任務(wù)棧

我們可以在adb shell中使用dumpsys activity可以看到任務(wù)棧。這個(gè)命令的打印會比較多,但是有兩個(gè)部分是比較重要的。

一個(gè)是Recent tasks,這里可以看到各個(gè)任務(wù)棧的總體信息,如我們的demo在第0個(gè)任務(wù)棧,它的Task id 是483,包名是linjw.demo.launchmodedem,棧里面有6個(gè)Activity

ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)
  Recent tasks:
  * Recent #0: TaskRecord{f69123d #483 A=linjw.demo.launchmodedemo U=0 sz=6}
  * Recent #1: TaskRecord{b798a32 #487 A=com.android.systemui U=0 sz=0}
  * Recent #2: TaskRecord{f874383 #486 A=com.tencent.mm U=0 sz=1}
  * Recent #3: TaskRecord{d7bd900 #479 A=com.meizu.flyme.launcher U=0 sz=1}
  * Recent #4: TaskRecord{8f5d797 #482 A=com.tencent.mobileqq U=0 sz=0}
  * Recent #5: TaskRecord{fd99539 #481 A=android.task.stk.task U=0 sz=0}
  * Recent #6: TaskRecord{7c9951e #480 A=com.android.incallui U=0 sz=1}
  * Recent #7: TaskRecord{e32177e #478 A=com.zhihu.android U=0 sz=0}
  * Recent #8: TaskRecord{7f094df #472 A=com.meizu.media.reader U=0 sz=0}

在它的下面可以看到里面的具體的activity,并且可以看到叫起它的Intent:

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
  Stack #1:
    Task id #483
      TaskRecord{f69123d #483 A=linjw.demo.launchmodedemo U=0 sz=6}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=linjw.demo.launchmodedemo/.FirstActivity }
        Hist #5: ActivityRecord{786c71e u0 linjw.demo.launchmodedemo/.FirstActivity t483}
          Intent { cmp=linjw.demo.launchmodedemo/.FirstActivity }
          ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
        Hist #4: ActivityRecord{6d5da5d u0 linjw.demo.launchmodedemo/.SecondActivity t483}
          Intent { cmp=linjw.demo.launchmodedemo/.SecondActivity }
          ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
        Hist #3: ActivityRecord{90b9f88 u0 linjw.demo.launchmodedemo/.FirstActivity t483}
          Intent { cmp=linjw.demo.launchmodedemo/.FirstActivity }
          ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
        Hist #2: ActivityRecord{1de66e u0 linjw.demo.launchmodedemo/.SecondActivity t483}
          Intent { cmp=linjw.demo.launchmodedemo/.SecondActivity }
          ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
        Hist #1: ActivityRecord{17dd5b1 u0 linjw.demo.launchmodedemo/.FirstActivity t483}
          Intent { cmp=linjw.demo.launchmodedemo/.FirstActivity }
          ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
        Hist #0: ActivityRecord{9f195f8 u0 linjw.demo.launchmodedemo/.FirstActivity t483}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=linjw.demo.launchmodedemo/.FirstActivity }
          ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
    ...

例如Hist #0這個(gè)Activity是從桌面點(diǎn)擊應(yīng)用圖標(biāo)進(jìn)入的,所以它的Intent帶android.intent.category.LAUNCHER這個(gè)category和FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY(0x00100000)、 FLAG_RECEIVER_FOREGROUND(0x10000000)這兩個(gè)Flag,而其他的Activity都是我們通過點(diǎn)擊按鈕叫起的,我們的Intent里面沒有帶任何的category和Flag,所以只有cmp標(biāo)識是哪個(gè)Activity。

我們可以通過Intent#setFlags(int) 設(shè)置Flag、Intent#addCategory(String) 添加category

有時(shí)候可以通過這個(gè)命令查看Activity的啟動模式結(jié)合各種launchMode的作用來定位一些bug

standard

standard是默認(rèn)的啟動模式,在沒有配置android:launchMode的時(shí)候就會默認(rèn)用這種啟動模式,當(dāng)然也可以顯示指定為standard。

它的特點(diǎn)是每次都會啟動一個(gè)新的Activity實(shí)例,我們連續(xù)點(diǎn)擊第一個(gè)按鈕兩次然后再連續(xù)點(diǎn)擊兩次返回鍵,截圖如下:

standard 啟動activity
standard 退出activity

任務(wù)棧狀態(tài)圖如下:

standard 任務(wù)棧

singleTop

當(dāng)launchMode是singleTop的時(shí)候,如果task棧的棧頂Activity和將要啟動的Activity是同一個(gè)Activity的話,就不會再啟動第二個(gè)Activity。我們將FirstActivity設(shè)為singleTop,在啟動demo之后無論按第一個(gè)按鈕多少次,任務(wù)棧里面都只會有一個(gè)FirstActivity。

singleTop 連續(xù)啟動同一個(gè)activity

任務(wù)棧狀態(tài)圖如下:

singleTop 連續(xù)啟動同一個(gè)activity任務(wù)棧

但是當(dāng)任務(wù)棧的棧頂Activity和將要啟動的Activity不是同一個(gè)Activity的時(shí)候,就會啟動新的Activity,并將它壓入棧頂而不管棧里面還有沒有這個(gè)Activity:

singleTop 啟動activity
singleTop 退出activity

任務(wù)棧狀態(tài)圖如下:

singleTop 任務(wù)棧

singleTask

我們將FirstActivity和SecondActivity的launchMode都設(shè)置為singleTask,啟動demo之后先按“GOTO SECOND ACTIVITY”再按“GOTO FIRST ACTIVITY”,截圖如下:

singleTask 啟動activity

我們可以看到在SecondActivity中啟動FirstActivity,結(jié)果就返回了第一個(gè)Activity。如果這個(gè)時(shí)候再按返回鍵就會推出應(yīng)用。

singleTask的作用就是在任務(wù)棧中尋找將要啟動的Activity,如果找到的話就將它上面的Activity都彈出棧,直到它成為棧頂。

任務(wù)棧狀態(tài)圖如下:

singleTask 任務(wù)棧

singleInstance

官方文檔的介紹是:

與“singleTask"”相同,只是系統(tǒng)不會將任何其他 Activity 啟動到包含實(shí)例的任務(wù)中。 該 Activity 始終是其任務(wù)唯一僅有的成員。

就是說系統(tǒng)會為singleInstance Activity單獨(dú)創(chuàng)建一個(gè)任務(wù)棧,這個(gè)任務(wù)棧里是這個(gè)Activity獨(dú)占的,不會再壓入其他的Activity。而且它是系統(tǒng)唯一的,當(dāng)singleInstance Activity已經(jīng)存在于系統(tǒng)的某一任務(wù)棧中,就會直接跳到那個(gè)任務(wù)棧的Activity中,而不會新啟動一個(gè)Activity。

我們將FirstActivity設(shè)為standard, SecondActivity設(shè)為singleInstance。啟動demo之后先按“GOTO SECOND ACTIVITY”再按“GOTO FIRST ACTIVITY”。然后再一直按返回鍵到退出應(yīng)用。截圖如下:

singleInstance 啟動 activity
singleInstance 退出activity

任務(wù)棧狀態(tài)圖如下:

singleInstance 任務(wù)棧

啟動應(yīng)用之后先點(diǎn)擊“GOTO SECOND ACTIVITY”,這個(gè)時(shí)候系統(tǒng)會新建一個(gè)任務(wù)棧(Task 20)來放SecondActivity

在SecondActivity中再啟動FirstActivity,因?yàn)門ask 20這個(gè)任務(wù)棧是SecondActivity獨(dú)占的。所以不會在這個(gè)任務(wù)棧壓入其他Activity,而會回到原來的任務(wù)棧上(Task 19)。又因?yàn)镕irstActivity的launchMode是standard,所以不管原來的棧里面有沒有FirstActivity,都會壓入一個(gè)新的FirstActivity。

這個(gè)時(shí)候再按返回鍵就不是回到SecondActivity了,因?yàn)樗谄渌娜蝿?wù)棧里面,要先將當(dāng)前任務(wù)棧清空。

這個(gè)時(shí)候按返回鍵會將當(dāng)前的Activity彈出棧,于是就跳到了一開始的FirstActivity。之后再按返回鍵,因?yàn)門ask 19這個(gè)任務(wù)??樟?就會去到SecondActivity的棧,于是就去到了SecondActivity。最后再按返回鍵就會退出應(yīng)用了。

要再次提醒需注意的是singleInstance的Activity是系統(tǒng)唯一的,也就是說你在demo這里啟動了這個(gè)SecondActivity的SecondActivity,然后按home鍵回到桌面去啟動其他應(yīng)用,從其他應(yīng)用再啟動一個(gè)SecondActivity也是去到原來的那個(gè)SecondActivity

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容