finish()ではプロセスは終わらない

Androidのメソッドの一つにfinish()というのがある。これはアクティビティを終了させたい時に呼び出す。のだが、気をつけないとハマるので、ちょっと注意が必要。

finish()を呼び出すとexitのようにプロセスが終了するかと思えば、そうではない。アクティビティは“停止状態”になり、(AndroidOSつまりLinuxの)プロセスとしては生き続けている。

試しに次のような簡単なプログラムを作ってテストしてみよう。

package com.example.android.finishtest;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class FinishTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        finish();
        Log.d("FinishTest", "Called finish()");
    }

    @Override
    protected void onDestroy() {
        Log.d("FinishTest", "onDestroy");
    	super.onDestroy();
    }
}

onCreateされた途端にfinishしてみる。このアプリを実行した時のLogの出力は以下の通り。

04-27 19:50:02.197: DEBUG/FinishTest(1145): Called finish()
04-27 19:50:02.727: DEBUG/FinishTest(1145): onDestroy

つまり、onCrateでfinish()を呼び出すと、このアクティビティをdestoryするようにスケジュールするだけで、またonCreateに戻ってくる。そしてfinish()の次の行にあるログ出力のメソッドを呼び出してからonCreateを抜ける。そして、その直後にonDestroyが呼び出されて、アクティビティはdestoryされる。

さて、destoryされた後のプロセスの状態を見てみると次のようになっている。

android@android:~$ adb shell
# ps
USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
root      1     0     296    204   c009a694 0000c93c S /init
	:
app_31    1145  30    127296 18644 ffffffff afe0da04 S com.example.android.finishtest
	:
#

と、確かに、まだプロセスとしては存在している。また、SDKのDDMS(Dalvik Debug Monitor Server)で眺めても確かにプロセスとして存在している。

つまり、finish()を呼び出してもアプリケーションとしては終了するもののプロセスとしては(他のアプリケーションの起動によりメモリが不足するなどの状態にならない限りは)存在し続けて、次回の起動時に再利用(リサイクル)され、onCreateが呼び出される(らしい)。メモリへの展開やライブラリとのリンクはコンピュータにとっては負担の大きな作業なので、携帯電話のような限られたハードウェアでは自然な方法だろう。

それがどうした?

finish()を呼び出してもプロセスとしては存在し続ける。だから、それがどうした? 次回の再利用でプロセスを再生成する必要がないので、いいじゃん。ハードウェアへの負担も小さいから、いいじゃん。

ところが、アプリを作っていて、ちょっとハマってしまった。

アクティビティのクラスを定義する際にスタティックなメンバ変数の初期化を宣言部分で行っていた。まぁ、普通にJavaでプログラムする場合の常套手段だ。スタティックなメンバ変数を宣言部で初期化するということは、そのクラスがメモリ上に展開された時に自動的に初期値が代入される。定数でなければ(つまりfinal宣言でなければ)プログラムの進行に伴い変数値も変化する。しかし、アプリケーションを終了して、再度起動した場合は自動的に初期化されるので、元の値に戻ってから始められる。

ところが、Androidのアクティビティはちょっと違う。上で検証したようにfinish()を呼び出しても、アクティビティはDestroyはされるものの、プロセスとして残っていて(つまりメモリ上にクラスがロードされた状態で残っていて)、次回、再利用する時にはスタティックなメンバ変数の初期化は行ってくれない。

ハマりました。先入観でfinish()は“exit()+α”程度に思っていたので。

アクティビティ開始時に値が定まっていなければならないメンバ変数の初期化は、宣言時ではなく、onCreate、もしくは必要の応じて、onStart、onResumeで実行する必要がある。勿論、プログラム開始時に値が未定であって構わない変数は気にする必要がないし、一方で初期化が必要な場合は初期化のタイミングによって、どのコールバックメソッドで初期化するか気をつけて決めなければならない。