ActivityのLaunchModeによる起動の違い

投稿日:  更新日:

起動されたActivityはAndroidの中で「スタックとタスク」という仕組みで管理されます。

LaunchModeはActivityを「スタックとタスク」の中へ配置する方法を決めます。

このLaunchModeがとても複雑で、アプリ開発で常に混乱を招く内容です。

なので、まとめました。

スポンサーリンク

スタックとタスク

Androidは画面を切り替える

アプリケーションがユーザにより多くの情報を伝えたい時、

Windowsのアプリは新たなウィンドウを開いて横に並べることが可能です。
画面が広いですから、表示に余裕が多く残っているからです。

Windows 情報の伝え方

一方、Androidのアプリは新たなウィンドウを開いて横に並べることはしません。
画面が狭いですから、一つの情報の表示に手一杯で余裕が無いからです。

なので、画面を切り替えるという手法をとります。伝えたい情報が発生したら、それを表示する画面に切り替えるというわけです。

Android 情報の伝え方

スタックとは

Androidは情報を表示する一つの画面を一つのActivityが受け持っています。

伝えたい情報が発生したらstartActicity( )で新たなActivityを開いて画面を切り替えます。

そして、Acitivityが必要なくなったらfinish( )で閉じて元の画面に戻ります。

図はその様子を示しています。

スタックの概要

内部では、startActivity( )を実行するとActivityのインスタンスは既存のActivityの上に積み上げられます。積み上げられた最上位のAcitvityをフォアグラウンドといい、フォアグラウンドが現在表示されているActivityです。

フォアグラウンドよりも下位をバックグラウンドといい、表示が隠されているActivityです。

フォアグラウンドのActivityが終了されると上から取り去られて、バックグラウンドの先頭がフォアグラウンドへ移行します。

このように「積み上げる・上から取り去る」という管理方法をスタック構造といいます。

タスクとは

スタック構造で管理される一連のActivityの固まりをAndroidではタスクと呼んでいます。

タスク内のActivityは、アプリを起動した時の最初のActivity(ルートアクティビティ)から派生したものです。なので、アプリとタスクは、ほぼ等しい存在です(厳密にはアプリは一つ以上のタスクで構成される)。

このタスクもスタック構造で管理されています。

図はその様子を示しています。

タスクの概要

アプリを起動するとタスクは既存のタスクの上に積み上げられます。スタック構造の最上位のタスクが現在表示されているアプリです。これにより新しいアプリに表示が切り替わります。

アプリの利用が終了されると上から取り去られて、バックグラウンドの先頭のタスクが最上位に移動します。これにより元のアプリに表示が戻ります。

スタックとタスクを確認

Activityとタスクのスタック構造を制御することで、Androiは表示対象を切り替えながら動作しています。

現状のスタックとタスクの状態はadb shellのdumpsys activityコマンドで確認できます。

$ dumpsys activity activities | grep -B 1 "Run #[0-9]*:"
      TaskRecord{19ad1d6 #56 A=com.example.myapp.Alpabet U=0 StackId=1 sz=4}
        Run #5: ActivityRecord{12e680  u0 com.example.myapp/com.example.myapp.SubActivity__D t56}
        Run #4: ActivityRecord{f60db5a u0 com.example.myapp/com.example.myapp.SubActivity__C t56}
        Run #3: ActivityRecord{21422e4 u0 com.example.myapp/com.example.myapp.SubActivity__B t56}
        Run #2: ActivityRecord{c0b8b8f u0 com.example.myapp/com.example.myapp.MainActivity_A t56}
      TaskRecord{37e22e8 #50 A=com.google.android.deskclock U=0 StackId=1 sz=1}
        Run #1: ActivityRecord{b615158 u0 com.google.android.deskclock/com.android.deskclock.DeskClock t50}
      TaskRecord{a67f7a6 #49 A=com.android.camera.CameraActivity U=0 StackId=1 sz=1}
        Run #0: ActivityRecord{ec129f3 u0 com.android.camera2/com.android.camera.CameraLauncher t49}
--
      TaskRecord{8c1afff #7 I=com.android.launcher3/.Launcher U=0 StackId=0 sz=1}
        Run #1: ActivityRecord{b1f5b83 u0 com.android.launcher3/.Launcher t7}
      TaskRecord{eb82b1e #10 A=com.android.systemui U=0 StackId=0 sz=1}
        Run #0: ActivityRecord{55b4fc u0 com.android.systemui/.recents.RecentsActivity t10}

Activityとタスクはスタック構造の上位から順に表示されています。例の場合、最上位のタスクは「#56」で、そのタスクのフォアグラウンドActivityは「#5」であることが読み取れます。

スポンサーリンク

LaunchMode

「スタックとタスク」の章はActivityを上に追加するだけの単純な配置方法しか出てきませんでした。

これ以外にもLaunchModeを指定して、配置方法を色々と変えることが出来ます。

LaunchModeを指定する方法は2つあります。

  • AndroidManifestにLaunchMode=”モード名”で指定(静的な指定)
  • Intent#setFlags(モードフラグ)で指定(動的な指定)

AndroidManifestにLaunchMode=”モード名”で指定

AndroidManifestで指定する方法は静的な指定です。

Activityの属性として指定するので、起動されるActivityは毎回この属性に従います。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	...
    <application
	...
        <activity android:name=".MainActivity_A"
            android:taskAffinity="com.example.myapp.Alpabet"
            android:launchMode="standard">
            ...
        </activity>
		<activity android:name=".SubActivity__B"
            android:taskAffinity="com.example.myapp.Alpabet"
            android:launchMode="standard"/>
        ...
        <activity android:name=".MainActivity_1"
            android:taskAffinity="ccom.example.myapp.Number"
            android:launchMode="standard"/>
    </application>
</manifest>

表は各モードの配置方法を表しています。

各モードに期待される基本な配置のみを記載しています。すべてIntent.setFlags()のフラグ指定(後述)がない場合です。

※配置方法はスタックとタスクの状態やIntent#setFlags(XXX)の指定により変わってきます。詳細は「LaunchMode=”XXX”とIntent#setFlags(XXX)の併用」を参照してください。

モード名配置先タスク配置方法
standard起動元のActivityを
含むタスク
新しいインスタンスをスタックの最上位に配置
LaunchMode="standard"
singleTop起動元のActivityを
含むタスク
タスクの先頭に同じインスタンスを重複して配置しない
(既存のインスタンスを再利用する)
LaunchMode="singleTop"
singleTask起動元のActivityを
含むタスク
タスクの中に同じインスタンスを重複して配置しない
(スタックの上位は削除、既存のインスタンスを再利用する)
LaunchMode="singleTask"
※FLAG_ACTIVITY_NEW_TASKフラグが自動で付加される
singleInstance起動元のActivityを
含むタスク
タスクの中にインスタンスを1つだけ配置
(タスクは異なるが同一のtaskAffinityを持つ)
LaunchMode="singleInstance"
※FLAG_ACTIVITY_NEW_TASKフラグが自動で付加される
※onCreate
 Activityのインスタンスが新規に作成されて、ライフサイクルがonCreate~onResumeへ遷移する
※onNewIntent()
 Activityのインスタンスが再利用されて、ライフサイクルがonNewIntent~onResumeへ遷移する
 この時、Intentが新しいAcitivityのものへ置き換わる

配置の基本は、配置先タスクが「起動元のActivityを含むタスク」になります。つまり、startActivity()を実行したActivityと同じタスクに積み上げられます。

Intent#setFlags(モードフラグ)で指定

Intent#setFlags()で指定する方法は動的な指定です。

Activityを起動するタイミングで指定するので、起動されるActivityのみがフラグに従います。

class SubActivity__C : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
		...
        findViewById<Button>(R.id.button).setOnClickListener {
            startActivity(
			    Intent(this, SubActivity__D::class.java)
				    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
			)
        }
		...
    }
}

表は各フラグの配置方法の違いです。

各フラグに期待される基本的な配置のみを記載しています。すべてLaunchMode=”standard”を指定(前述)した場合です。

※配置方法はスタックとタスクの状態やLaunchMode=”XXX”の指定により変わってきます。詳細は「LaunchMode=”XXX”とIntent#setFlags(XXX)の併用」を参照してください。

フラグ名廃止先タスク配置方法
フラグ無し起動元のActivityを
含むタスク
新しいインスタンスをスタックの最上位に配置
フラグ無し
NEW_TASK新規作成したタスク新しいインスタンスを新規作成したタスクへ配置
(2つのタスクは異なるtaskAffinityを持つ)
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
SINGLE_TOP起動元のActivityを
含むタスク
タスクの先頭に同じインスタンスを重複して配置しない
(既存のインスタンスを再利用する)
setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
CLEAR_TOP起動元のActivityを
含むタスク
タスクの中に同じインスタンスを重複して配置しない
(スタックの上位は削除、新しいインスタンスを作成する)
setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
※onCreate
 Activityのインスタンスが新規に作成されて、ライフサイクルがonCreate~onResumeへ遷移する
※onNewIntent()
 Activityのインスタンスが再利用されて、ライフサイクルがonNewIntent~onResumeへ遷移する
 この時、Intentが新しいAcitivityのものへ置き換わる

配置の基本は、配置先タスクが「起動元のActivityを含むタスク」になります。つまり、startActivity()を実行したActivityと同じタスクに積み上げられます。

ただし、FLAG_ACTIVITY_NEW_TASKフラグを持ち、taskAffinity(タスクの親和性、後述)が指定されたActivityは新規作成したタスクが配置先になります。

taskAffinity(タスクの親和性)

taskAffinityはActivityに付けられた「なかよしグループ名」のようなものです。

タスク名ではありません。

taskAffinityはAndroidManifestで指定を行います。

デフォルトはパッケージ名です。なので、何も指定しなければパッケージ名になります。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	...
    <application
	...
        <activity android:name=".MainActivity_A"
            android:taskAffinity="com.example.myapp.Alpabet"
            android:launchMode="standard">
            ...
        </activity>
		<activity android:name=".SubActivity__B"
            android:taskAffinity="com.example.myapp.Alpabet"
            android:launchMode="standard"/>
        ...
        <activity android:name=".MainActivity_1"
            android:taskAffinity="ccom.example.myapp.Number"
            android:launchMode="standard"/>
    </application>
</manifest>

taskAffinityの効果が発揮されるのはFLAG_ACTIVITY_NEW_TASKフラグが指定された時です。

通常のActivityの配置先は「起動元のActivityを含むタスク」になります。

しかし、FLAG_ACTIVITY_NEW_TASKフラグを持ち、起動元のActivityを含むタスクとtaskAffinityが異なっているActivityは、新規作成したタスクが配置先になります。

ちなみに、taskAffinityは一意な名前(世界でただ一つ)を付ける必要があります。従って、パッケージ名を頭に付けるのが通例のようです。

LaunchMode=”XXX”とIntent#setFlags(XXX)の併用

Activityの配置方法はLaunchModeだけでなく、現状のスタックとタスクの状態に影響されて変わってきます。

組み合わせが多くて複雑です。

以下に色々な組み合わせを図にまとめました。参考にしてください。

図は1行目がActivityの追加動作(Sub3からstartActivity()を実行)、2行目移行が追加後のスタックとタスクの状態です。各行の右端に併用したフラグ名が書かれています。

LaunchMode=”standard”の場合

standardはデフォルトです。

AndroidManifestに何も指定しなければstandardになります。

standardとフラグの併用

LaunchMode=”singleTop”の場合

singleTopとフラグの併用

LaunchMode=”singleTask”の場合

singleTaskはFLAG_ACTIVITY_NEW_TASKフラグが自動で付加されます。

そのため、SubAを起動した時(右端の列)にタスクが新規作成されています。

singleTaskとフラグの併用

LaunchMode=”singleInstance”の場合

singleInstanceはFLAG_ACTIVITY_NEW_TASKフラグが自動で付加されます。

そのため、SubAを起動した時(右端の列)にタスクが新規作成されています。

singleInstanceとフラグの併用

スポンサーリンク

LaunchModeで注意する事

Context#startActivity( )はNEW_TASKフラグが自動で指定される

Activity#startActivity()とContext#startActivity()を実行して、起動されたActivityでフラグの状態を確認してみます。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
	    ....
        findViewById<TextView>(R.id.txt2).setOnClickListener {
            applicationContext.startActivity(Intent(this, SubActivity__C::class.java))
			    または
            this.startActivity(Intent(this, SubActivity__C::class.java))
        }
    }
class SubActivity__C : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        showIntentFlag("Sub_C", this.intent)  // フラグの状態を確認
    }
}

fun showIntentFlag(mesg: String, intent: Intent) {
    val _flags = intent.flags

    val _buff = StringBuffer("%s[0x%08x]\n".format(mesg, _flags))
    if((_flags and Intent.FLAG_ACTIVITY_NEW_TASK) > 0) _buff.append("NEW_TASK\n")
    if (_flags and Intent.FLAG_ACTIVITY_SINGLE_TOP > 0) _buff.append("SINGLE_TOP\n")
    if (_flags and Intent.FLAG_ACTIVITY_CLEAR_TOP > 0) _buff.append("CLEAR_TOP\n")
    //
    if (_flags and Intent.FLAG_ACTIVITY_NO_HISTORY > 0) _buff.append("NO_HISTORY\n")
    if (_flags and Intent.FLAG_ACTIVITY_CLEAR_TASK > 0) _buff.append("CLEAR_TASKY\n")
    if (_flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT > 0) _buff.append("BROUGHT_TO_FRONT\n")
    if (_flags and Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS > 0) _buff.append("EXCLUDE_FROM_RECENTS\n")
    if (_flags and Intent.FLAG_ACTIVITY_FORWARD_RESULT > 0) _buff.append("FORWARD_RESULT\n")
    if (_flags and Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT > 0) _buff.append("LAUNCH_ADJACENTY\n")
    if (_flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY > 0) _buff.append("LAUNCHED_FROM_HISTORY\n")
    //
    if (_flags and Intent.FLAG_ACTIVITY_MULTIPLE_TASK > 0) _buff.append("MULTIPLE_TASK\n")

    Log.i("ShowIntentFlags", _buff.toString())
}

Activity#startActivity()で起動した場合のフラグ

I/ShowIntentFlags: Sub_C[0x00000000]

Context#startActivity()で起動した場合のフラグ

I/ShowIntentFlags: Sub_C[0x10000000]
    NEW_TASK

Context#startActivity()で起動した場合にはFLAG_ACTIVITY_NEW_TASKフラグが自動で不可される事が確認できます。

アプリのプロセスが最初にActivityを起動する時、新規にタスクを作成する必要があるためだと思われます。

スポンサーリンク

関連記事:

Activityはライフサイクルを持っています。ライフサイクルは「画面の作成~表示~破棄」という一連の流れのことです。 アプリケーションはこの流れに沿ってプログラミングします。流れに逆らったプログラムは作れません。 アプリケーションの開発者にとって、ライフサイクルの理解は最重要です。 Activityのライフサイクルについてまとめます。 ...
アプリを構成する最上位の構成要素がアプリケーションコンポーネント(App component)です。 アプリケーションコンポーネントの概要をまとめます。 ...
「インテントの解決」はシステム内でアプリケーションコンポーネントを選択する処理のことです。 この選択する処理の流れが複雑で、ちょっと癖がありあます。 ここでは「インテントの解決」の処理の流れについてまとめます。 ...
アプリを構成する最上位の構成要素がアプリケーションコンポーネント(App component)です。 Activityはアプリケーションコンポーネントの1つです。アプリケーションの画面と処理を構築します。 Activityについてまとめます。 ...
Applicationクラスのインスタンスは、アプリが起動する際に作成されて、アプリが起動している限り存在し続けます。 しかも、全てのアプリケーションコンポーネントから参照可能なインスタンスです。 ですので、アプリ全体に関連する共有データを管理する場合に便利です。 例えば、「アプリの環境設定データ」などが考えられます。 今回は、このApplicationクラスの使い方を、まとめます。 ※環境:Android Studio Iguana | 2023.2.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1 ...
スポンサーリンク