LoginSignup
29
25

More than 5 years have passed since last update.

singleTask/singleInstanceなActivityで、2つの別インスタンスが共存してしまうケース

Last updated at Posted at 2015-03-09

launchModeがsingleTasksingleInstanceであるActivityのインスタンスを、完全なsingletonのつもりで設計していたら、えらくハマったのでメモまで。

要は、singleTask / singleInstanceなActivityでfinish()メソッドを呼んだ後に、自分自身を再起動すると、別々のインスタンスで共存する区間ができてしまう。

(おさらい1) 通常のActivity遷移

Activity A からActivity BstartActivity()で起動する場合。
ActivityのlaunchModeはstandardであるとしておく。

このとき、Activityのライフサイクルにまつわるメソッドは以下のような順序で呼ばれる。

  • A から B をstartActivity()
  • A: onPause() ※1
  • B: onCreate()
  • B: onStart()
  • B: onResume()
  • A: onStop() ※2

ポイントは2箇所。

  • ※1 起動元 AonPause() 処理が終わらないと、起動先の BonCreate() は呼ばれない
  • ※2 起動元 AonStop()onDestroy() は、起動先の BonResume() の後に呼ばれる模様。 ただし、A のonStop()やonDestroy() は状況によってはそもそも呼ばれないことがある。(Aの画面が隠れず見えている場合など)

※1については、気にしている人が少ないようで、onPause()で重い処理を書いてしまうと、画面遷移における速度低下の原因になってしまう。

(おさらい2) singleTask / singleInstance な Activityの再起動

launchModeがsingleTaskもしくはsingleInstanceであるActivity A について、A から A を起動(再起動)する。

「同じActivityに再起動させることなんてあるのか」と突っ込まれそうだが、起動引数に応じて画面の表示内容を更新する場合などに利用できる。

このとき、Activityのライフサイクルにまつわるメソッドは以下のような順序で呼ばれる。

  • A から A をstartActivity()
  • A: onPause()
  • A: onNewIntent()
  • B: onResume()

ポイントは、singleTasksingleInstanceの場合は、起動元と同じActivityインスタンスが呼び出される。
そのため、onCreate() は呼ばれず、代わりに onNewIntent()で再起動時に指定するIntentが渡される。

(本題) 別々のActivityインスタンスが共存するケース

上記の (おさらい2) とほとんど同じだが、singleTaskもしくはsingleInstanceの Activity A について、 A から A を起動 (再起動) する。 ただし、再起動前に、自分自身を終了させるため finish() メソッドを実行する。

例えば、以下のような感じ。

MainActivity.java
     : ()

    ((Button) findViewById(R.id.button))
            .setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 一旦、自分自身を終了してから、
                finish();

                // 続けて、自分自身を起動する。
                Intent i = new Intent(MainActivity.this, MainActivity.class);
                startActivity(i);
            }
        });

     : ()

このとき、Activityのライフサイクルにまつわるメソッドは以下のような順序で呼ばれる。

  • Aをfinish()し、続けて A から A をstartActivity()
  • A: onPause()
  • A': onCreate() -- (1)
  • A': onStart()
  • A': onResume()
  • A: onStop()
  • A: onDestroy() -- (2)

ここで A' はActivity A とは別に生成されるActivityインスタンス。
上記のとおり、(1) から (2) の間は、singleTask / singleInstance であるActivityのインスタンスが2個共存することになり、こうしたActivityのインスタンスはVM上で完全なsingletonではないことが分かる。

共存区間(1)-(2)は刹那なため、実影響は少ないように思えるが、自身のハマりケースの場合、Activityとは別のアプリコンポーネントの状態制御をonPause()やonDestroy()などのメソッドをトリガーに行うよう設計していて(設計されていて)、どハマりした。上記のように、finish()される A インスタンスの onDestroy() が最後っ屁のように実行されてしまうため、意図しない終了処理が実行されてしまう。

また余談だが、finish() メソッドもかなりのクセもの。

Activityの onDestory() が確実にCallされる目的とかで、開発終盤のバグFixフェイズにしれっとまぎれてくることがあるが、Activityのライフサイクルの見通しを悪くしてしまうので、多用を避けることをお勧めしたい。

29
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
25