2013 年 11 月 9 日

Percelでアプリの状態を保存・復元

9999-logo

アンドロイドのアクティビティは、バックグラウンドにまわされると、OSからいつ終了(onDestoryを呼出)させられてもおかしくない状態になります。

たとえば、アプリから異なるアプリのアクティビティをインテントで呼び出した後、このアクティビティからの応答を待っているときに、一旦終了させられて、応答が返ったとき再生成されonCreateが呼出させることがあります。

このとき、アプリのアクティビティの状態(たとえば、選択されたタブとか、リストのチェックなど)を復元してやる必要があります。アクティビティが新たに起動しonCreateが呼出されるので、こういった情報は残りません。

これはアンドロイドのアクティビティのライフサイクルの特徴で、アプリケーションプログラマは、これを想定してアプリを開発する必要があります。

Parcelableを実装

数少ない情報の保存ならBundleにそのまま保存すればよいですが、ある程度まとまっていたり、アンドロイドのAPIからのクラスインスタンスなども保存する必要がある場合、Parcelを利用する方がよいでしょう。

Parcelとは小包というような意味ですが、Parcelableインタフェースを実装しているクラスは、簡単にBundleに保存することができます。

ParcelableはJavaのserializableインタフェースと同じような働きを持っており、オブジェクトを直列化します。
ParcelableはIntentを使ったアプリ間の移動にも利用できますが、serializableは同一アプリ間なら利用可能なようです。

今、アクティビティの状態として、選択しているタブnoとタブ名を保存・復元しなければならないとし、以下のようなPOJOを定義します。

public class ActivityInfo {
  private int mTabNo;
  private String mTabName;

  public ActivityInfo(int no, int name) {
    mTabNo = no;
    mTabName = name;
  }
}

これにParcelableインタフェースを実装すると、以下のようになります。

public class ActivityInfo implements Parcelable {
  private int mTabNo;
  private String mTabName;

  public ActivityInfo(int no, int name) {
    mTabNo = no;
    mTabName = name;
  }

  /**
   * Parcelableの実装
   */
  public static final Parcelable.Creator<ActivityInfo> CREATOR = new Parcelable.Creator<ActivityInfo>() {  
    
    public ActivityInfo createFromParcel(Parcel in) {  
      return new ActivityInfo(in);  
    }  

    public ActivityInfo[] newArray(int size) {  
      return new ActivityInfo[size];  
    }  
  };
  
  /**
   * 読み出し順と書き出し順と、それぞれの変数の型を一致させる。
   */
  public ActivityInfo(Parcel in) {
    mTabNo = in.readInt();
    mTabName = in.readString()
  }
  
  @Override
  public int describeContents() {  
    return 0;  // 通常は0を返す。
  }
  
  /**
   * 読み出し順と書き出し順と、それぞれの変数の型を一致させる。
   */
  @Override
  public void writeToParcel(Parcel out, int flags) {
    out.writeInt(mTabNo);
    out.writeString(mTabName);
  }
}

これで、アクティビティの情報を保存・復元する準備が整いました。もし、アンドロイドのAPIのクラスインスタンスも保存したい場合は、そのクラスがParcelabelを実装しているかどうかを調べて、実装しているなら、一緒に保存・復元可能です。

保存と復元

では、どこでこれらの情報を保存・復元すればいいかというと、保存はonSaveInstanceStateメソッドをOverrideします。復元はonRestoreInstanceStateメソッドをOverrideするのですが、試してみるとonCreate(Bundle savedInstanceState)のsavedInstanceStateから復元できるようです。復元時のライスサイクルメソッドの呼出順は、onCreate->onRestoreInstanceStateとなっています。startActivityForResultで他のアクティビティを呼出中なら、この後にonActivityResultが呼出されます。

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /**
     * onCreateで復元することも可能なようだ。
     */
    if (savedInstanceState != null) {
      ActivityInfo info = (ActivityInfo[])savedInstanceState.getParcelable("activityinfo");
    }
  }
  /**
   * 保存する。
   */
  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    ActivityInfo info = new ActivityInfo(1, "tab");
    outState.putParcelable("activityinfo", info);
  }
  /**
   * 復元する。
   */
  @Override
  protected void onRestoreInstanceState(Bundle savedInstanceState)  {
    super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState != null) {
      ActivityInfo info = (ActivityInfo[])savedInstanceState.getParcelable("activityinfo");
    }
  }

アクティビティのオリエンテーションが変わったときも、onCreateが新たに呼ばれるので、同様に保存・復元する必要があります。たとえ、オリエンテーションを固定していても(android:configChangesを使っても同じ)、アンドロイドのアクティビティの仕様により、このような実装は必須です。