インテントフラグとActivityStackのふるまいについていくらか
Android
の Activity
のスタックの振る舞いを設定するものとして、
AndroidManifest.xml
に記述するactivity
要素のlaunchMode
属性Activity
を立ち上げる時に使うIntent
に付与するインテントフラグ
があります。先日、launchModeの話をしたので、今日はインテントフラグの実験を少ししようかと思います。
今回インテントフラグがどんなものか確認する際に取り上げる具体例としては、
あたりにします。今回の実験に使ったコードの全体像はこちらで適宜、/* some flags */
周辺のコードを編集した分を実行結果と一緒に紹介します。
TL;DR
長くなったので。
- FLAG_ACTIVITY_SINGLE_TOP
launchMode
のsingleTop
と同じ- このフラグをつけて起動したActivityと同じActivityがスタックの一番上にあるなら、既存のインスタンスの
onNewIntent()
を呼びだす
- FLAG_ACTIVITY_CLEAR_TOP
- FLAG_ACTIVITY_NO_HISTORY
- このフラグをつけて起動した
Activity
はActivity
のスタックに積まれない - =
onActivityResult
が決して呼び出されない
- このフラグをつけて起動した
FLAG_ACTIVITY_SINGLE_TOP
まず、MainActivity
を起動するときだけ、FLAG_ACTIVITY_SINGLE_TOP
を利用してどのような挙動をするか見てみましょう。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, NextActivity.class); startActivity(intent); } }); }
public class NextActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_next); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, NextActivity.class); startActivity(intent); } }); }
MainActivityが一番上にあるときはその上にActivityが積まれず
NextActivityが一番上にあるときは新しいMainActivityが積まれます。
launchMode
の singleTop
と同じ振る舞いですね。
FLAG_ACTIVITY_CLEAR_TOP
MainActivityを表示しようとしたとき、FLAG_ACTIVITY_CLEAR_TOP
のインテントフラグを付与することにします。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("MainActivity", "MainActivity " + MainActivity.this.toString()); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, NextActivity.class); startActivity(intent); } }); }
public class NextActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_next); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, NextActivity.class); startActivity(intent); } }); }
MainActivityが一番上にあるとき、すでにあるActivityが破棄されてから別のActivityが作り直されて一番上に積まれ直します。
Log.d("MainActivity", "MainActivity " + MainActivity.this.toString());
でインスタンスが変わっているというログがこちらです。
D/MainActivity: MainActivity com.example.woshidan.intentflagtest.MainActivity@3a2375cd D/MainActivity: MainActivity com.example.woshidan.intentflagtest.MainActivity@21a00ad7 D/MainActivity: MainActivity com.example.woshidan.intentflagtest.MainActivity@ac67751
何枚か NextActivity
をスタックに積んでから MainActivity
を起動し、その後バックキーを押すとアプリが終了します。
この場合のLog.d("MainActivity", "MainActivity " + MainActivity.this.toString());
でインスタンスが変わっているというログがこちらです。
D/MainActivity: MainActivity com.example.woshidan.contexttest.MainActivity@2007c35 D/MainActivity: MainActivity com.example.woshidan.contexttest.MainActivity@25e3619b
もう少し別の条件で検証するとわかりやすいですが、起動しようとした MainActivity
より上のスタックにある NextActivity
と古い MainActivity
を捨て、MainActivity
を作り直して、呼び出していることがわかります。
ドキュメントのフラグ一覧のところの FLAG_ACTIVITY_CLEAR_TOP
の欄には、
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
とあり、これは上記の挙動と異なるように見えます。
しかし、これは仕様どおりで、さらに FLAG_ACTIVITY_CLEAR_TOP
の単体の項目のドキュメントを読むと、FLAG_ACTIVITY_SINGLE_TOP
との併用か、 Activity
の launchMode
が singleTop
に設定されていなければ、既存のインスタンスへちょっとIntentが送られないことがあることがわかります。
それでは、 FLAG_ACTIVITY_CLEAR_TOP
とFLAG_ACTIVITY_SINGLE_TOP
との併用した場合の挙動を
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("MainActivity", "MainActivity " + MainActivity.this.toString()); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, NextActivity.class); startActivity(intent); } }); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.d("MainActivity", "onNewIntent"); Toast.makeText(this, intent.getStringExtra("KEY_MESSAGE"), Toast.LENGTH_SHORT).show(); }
public class NextActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_next); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, NextActivity.class); startActivity(intent); } }); } }
で試してみましょう。今度は、一覧の部分にあるように既存のMainActivityのインスタンスにIntentが送られるようになったみたいです。
FLAG_ACTIVITY_NO_HISTORY
どのActivityを起動するときも FLAG_ACTIVITY_NO_HISTORY
のインテントフラグを付与してがんがんActivityを起動してみます。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, NextActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); startActivity(intent); } }); }
public class NextActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_next); findViewById(R.id.button_launch_first_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, MainActivity.class); intent.putExtra("KEY_MESSAGE", "sent new intent"); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); startActivity(intent); } }); findViewById(R.id.button_launch_second_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(NextActivity.this, NextActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); startActivity(intent); } }); } }
たくさんスタックを積んだはずが、2回バックキーを押すとアプリが終了してしまいました。
最初の一回のバックキーで一番最初に起動した MainActivity
へ戻ってしまったようです。
FLAG_ACTIVITY_NO_HISTORY
のインテントフラグをつけて起動されたActivityは、Activityのスタックに積まれないようです。
インテントフラグはActivityを起動する側が指定するので、使う側の都合によってActivityのスタック上での扱いを決める方法、みたいな感じなんですかね。
現場からは以上です。