Android溫故而知新 - launchMode

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

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

測試demo

我們通過兩個Activity之間的跳轉和任務棧打印來理解launchMode的作用。

這兩個Activity使用同一個布局, TextView用來打印任務棧的id和activity實例,然后是兩個按鈕,分別用來啟動兩個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());
    }
}

因為兩個activity的代碼很相似,所以我把它們放到了靜態類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);
    }
}

查看任務棧

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

一個是Recent tasks,這里可以看到各個任務棧的總體信息,如我們的demo在第0個任務棧,它的Task id 是483,包名是linjw.demo.launchmodedem,棧里面有6個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這個Activity是從桌面點擊應用圖標進入的,所以它的Intent帶android.intent.category.LAUNCHER這個category和FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY(0x00100000)、 FLAG_RECEIVER_FOREGROUND(0x10000000)這兩個Flag,而其他的Activity都是我們通過點擊按鈕叫起的,我們的Intent里面沒有帶任何的category和Flag,所以只有cmp標識是哪個Activity。

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

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

standard

standard是默認的啟動模式,在沒有配置android:launchMode的時候就會默認用這種啟動模式,當然也可以顯示指定為standard。

它的特點是每次都會啟動一個新的Activity實例,我們連續點擊第一個按鈕兩次然后再連續點擊兩次返回鍵,截圖如下:

standard 啟動activity
standard 退出activity

任務棧狀態圖如下:

standard 任務棧

singleTop

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

singleTop 連續啟動同一個activity

任務棧狀態圖如下:

singleTop 連續啟動同一個activity任務棧

但是當任務棧的棧頂Activity和將要啟動的Activity不是同一個Activity的時候,就會啟動新的Activity,并將它壓入棧頂而不管棧里面還有沒有這個Activity:

singleTop 啟動activity
singleTop 退出activity

任務棧狀態圖如下:

singleTop 任務棧

singleTask

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

singleTask 啟動activity

我們可以看到在SecondActivity中啟動FirstActivity,結果就返回了第一個Activity。如果這個時候再按返回鍵就會推出應用。

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

任務棧狀態圖如下:

singleTask 任務棧

singleInstance

官方文檔的介紹是:

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

就是說系統會為singleInstance Activity單獨創建一個任務棧,這個任務棧里是這個Activity獨占的,不會再壓入其他的Activity。而且它是系統唯一的,當singleInstance Activity已經存在于系統的某一任務棧中,就會直接跳到那個任務棧的Activity中,而不會新啟動一個Activity。

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

singleInstance 啟動 activity
singleInstance 退出activity

任務棧狀態圖如下:

singleInstance 任務棧

啟動應用之后先點擊“GOTO SECOND ACTIVITY”,這個時候系統會新建一個任務棧(Task 20)來放SecondActivity

在SecondActivity中再啟動FirstActivity,因為Task 20這個任務棧是SecondActivity獨占的。所以不會在這個任務棧壓入其他Activity,而會回到原來的任務棧上(Task 19)。又因為FirstActivity的launchMode是standard,所以不管原來的棧里面有沒有FirstActivity,都會壓入一個新的FirstActivity。

這個時候再按返回鍵就不是回到SecondActivity了,因為它在其他的任務棧里面,要先將當前任務棧清空。

這個時候按返回鍵會將當前的Activity彈出棧,于是就跳到了一開始的FirstActivity。之后再按返回鍵,因為Task 19這個任務棧空了,就會去到SecondActivity的棧,于是就去到了SecondActivity。最后再按返回鍵就會退出應用了。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容