画面回転時の挙動

端末を横に傾けると、重力センサで画面も横向きになりますよね。使う分には便利なんだけど、作る側は考えることが増えて大変です。資料も今一整備されていないので、分かってる所まで書いてみます。

何が大変かというと、再レイアウトされた時にActivityのライフサイクルに従い、onDestroyされ、onCreateされます。何せonDestroyまでされちゃうので、画面の保存をどうするかが問題になります。例えば入力中のテキストボックスだとか、表示中のダイアログだとか。
いや、実は入力中のテキストボックスや表示中のダイアログは、画面を回転させても状態が保存されるのです。ちょっとやってみます。

Viewで対応する方法


EditTextとTextViewとButtonを置いたActivityを作り、内容を変化させてみます。コードはこんなの

public class Test extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        EditText editText = (EditText)findViewById(R.id.EditText01);
        TextView textView = (TextView)findViewById(R.id.TextView01);
        Button b = (Button)findViewById(R.id.Button01);
        b.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v) {
                textView.setText("CHANGE!");
                textView.setBackgroundColor(Color.RED);
                editText.setText("CHANGE!");
                editText.setBackgroundColor(Color.RED);
            }
        });
    }
}

ボタンを押すと内容が変化します。

画面を回転させてみると(エミュレータだとCTRL+F11)・・

EditTextにsetTextした内容以外、元に戻ってしまいました。おそらく、EditTextは利用者が操作して内容を変化させるため、状態保存がされるのだと思います。プログラム的に内容を変えるものは、プログラムで対応しなくてはならないのかなと。
で、どうするのかですが、EditText(TextView)のソースを見てみると、onSaveInstanceState()で状態を保存して、onRestoreInstanceState()メソッドで復元しているみたいです。
かなり適当に抜粋したので、詳しくはAndroidのソースをダウンロードして見てください。

public Parcelable onSaveInstanceState() {
   Parcelable superState = super.onSaveInstanceState();
   SavedState ss = new SavedState(superState);
   ss.selStart = start;
   ss.selEnd = end;
   ss.text = sp;
   return ss;

public void onRestoreInstanceState(Parcelable state) {
    SavedState ss = (SavedState)state;
    super.onRestoreInstanceState(ss.getSuperState());
    if (ss.text != null) {
        setText(ss.text);
    }

public static class SavedState extends BaseSavedState {
    int selStart;
    int selEnd;
    CharSequence text;
    boolean frozenWithFocus;
    CharSequence error;
    SavedState(Parcelable superState) {
        super(superState);
    }

Parcelableが何なのか良くわかってないです。オブジェクト自体は完全に作り直されるので、どこかに値を退避させてるのだと思いますが。
このメソッドが呼ばれるタイミングは、onSaveはonStopの前、onRestoreはonCreateの後です。なので、onResumeでEditTextの初期化をすると、値が元に戻ってしまいます。(onRestoreInstanceStateで値の復元をした後に、onResumuが走るため。)そのあたりも注意しないといけませんね。画面の初期化はonCreateでやるべきなのだと思います。
あと、これは永続的な値の保存方法では無いのと、ライフサイクルに含まれていない動作で、全てのタイミングで実行されるわけではないので一時的な状態保存と復元以外には使えません。例えばアプリの終了まですると元に戻ってしまいます。そういう場合はActivityのonStop()のタイミングで、ファイルやContentProviderに書き出すべきです。
画面の回転の挙動に関しては最適な方法だと思いますね。ただし、Viewを自作しないといけないので大変です。

Activityで対応する方法

Viewを作るのは大変なので、Activityで対応する方法を説明します。ActivityにもonSaveInstanceStateとonRestoreInstanceStateメソッドがあるので、オーバーライドして保存と復元の方法を書いてやればOKです。Bundleを引数で取っているので設定は簡単です。
ドキュメントにも少し書かれてました。
Activity | Android Developers
もちろん、これも一時的な方法であり、永続的なデータの保存に関してはContentProvider等を使用しないといけません。

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);        
        outState.putString("STRING",textView.getText().toString() );
    }
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        textView.setText(savedInstanceState.getString("STRING"));        

    }

これでTextViewの値も保存復元されるようになりました。