Android アプリの間違った作り方 の続き


ずっと前に「Android アプリの間違った作り方」という記事を書いたけど、
見なおしてみると色々アレだったので、今ならこうするというのを書いておく

Activity の初期化と終了処理

Activityはバックグラウンド状態になったり画面を回転させたりすると一時的に破棄/復元される事がある。プロセスごと一時的に破棄される場合もあるので、例えばstatic変数に状態を保存しても復元の際にそれが維持されているとは限らない。初期化処理と終了処理を書く際に破棄と復元を考慮するには以下のような工夫が必要になる。

onCreate(), onNewIntent(), onSaveInstanceState(), onRestoreInstanceState()

あるActivityが作成された時に、それが最初に作られたのか、一時的に破棄された後に復元されたのかはonCreateの引数がnullかどうかで切り分けられる。この引数に渡されるBundleは onSaveInstanceState() で設定したものと同じ内容になる。

void onCreate(Bundle state){
	initUI(); // ActivityのUIを初期化する
	if( state != null ){
		// このActivityは復元されたものなので、
		// state を使って状態を復元する
		restoreState( state );
	}else{
		// このActivityは新規に作成されたものなので、
		// インテントを使って状態を初期化する
		initState( getIntent() );
	}
}

void onNewIntent(){
	// あるActivityが新しいIntentを受け取った時に呼ばれる
	// インテントを使って状態を初期化する
	initState( getIntent() );
}

void onSaveInstanceState(Bundle state){
	super.onSaveInstanceState(state)
	// TODO: state に画面の現在の状態を保存する
}
void onRestoreInstanceState(Bundle state){
	// state を使って状態を復元する
	restoreState( state );
}

void initUI(){
	// UIの初期化を行う
}
void initState(Intent intent){
	// TODO: intent の情報を使って状態を初期化する
}
void restoreState(Bundle state){
	// TODO: state の情報を使って状態を復元する
	restoreState( state );
}
isFinishing()

Activityの 終了処理をonPause,onStop,onDestoroy に書く際、一時的な破棄とfinish()による破棄を区別するには isFinishing() を使う。あるActivityが一時的に破棄される際は isFinishing()はfalseを示す。

アプリケーションレベルの状態の管理

もしあなたのアプリAで「アプリ単位の状態管理」を実現したいなら、まずはAndroidはアプリ単位の起動状態なんて管理していないことを知っておいて欲しい。特に何も指定しない場合、アプリAに含まれるActivityは別のアプリBの画面スタックの末尾に積まれることもあるし単独の画面スタックに積まれることもある。公式ドキュメントのタスクアフィニティについての記述を参照。
http://developer.android.com/guide/components/tasks-and-back-stack.html#Affinities
それでもアプリケーションレベルの状態を管理したいなら、以下の手法が使えるかもしれない。

アプリケーション単位の初期化を行うタイミング
  • 外部から起動されるActivityの種類を1個だけにする
  • そのActivityに android:launchMode="singleTask" を指定する

この状態でそのActivityのonCreate()が呼ばれて、それが復元ではない(Bundle引数がnull)場合、その時点でアプリ単位の状態を初期化していい。

アプリケーション単位の状態の保存と復元

これだけだとプロセスごと破棄された場合に状態が失われてしまうので、
アプリ単位の状態をフラッシュメモリに保存する仕掛けを入れる。
画面スタックの底にあるアクティビティとそこから呼び出される子アクティビティの全てに以下のような記述を行う。

(manifestに登録したアプリケーションクラス)
class App1 extends Application{
    static MyAppState app_state;

    void saveAppState(){
        is( app_state != null ){
            // TODO: アプリ単位の状態をフラッシュメモリに保存する
        }
    }
    
    void prepareAppState(Activity activity,Bundle activity_state){
        if( activity_state == null &&  activity is (画面スタックの底にあるアクティビティ) ) ){
            // 初回起動なので、状態を初期化する
            // もし直前までの状態が残っていればその後処理を行ってもよい
            app_state = new MyAppState();
        }else if( app_state == null ){
            // TODO: フラッシュメモリから状態を読み込む
        }
    }
}

(Activity派生クラス全て)
void onPause(){
    super.onPause();
    ((App1)getApplication()).saveAppState();
}
void onCreate(Bundle state){
    super.onCreate();
    ((App1)getApplication()).prepareAppState(activity, state);
}

この方法には問題もあって、外部から呼び出せるアクティビティが1個に限られてしまうし、
それが呼び出されるたびにアクティビティの画面スタックのルート以外の部分が全て一旦クリアされてしまう。

アプリ単位の揮発性の状態を管理する、もっとイイ手法があれば誰か教えてください。