Skip to content

Instantly share code, notes, and snippets.

@anolivetree
Last active September 14, 2015 08:23
Show Gist options
  • Save anolivetree/dd9963f137ebc721ae0f to your computer and use it in GitHub Desktop.
Save anolivetree/dd9963f137ebc721ae0f to your computer and use it in GitHub Desktop.
Activityの再生成にまつわる処理の煩雑さを解消するクラス
package com.example.retain;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Bundle;
import android.os.Parcelable;
import java.io.Serializable;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* activity/fragmentのrecreationの時に、processが生きている限りはbundleよりも多くの情報を記録しておいてくれるクラス
* processが死んだ場合には、bundleの情報までしか復帰されない。
*
* 使い方
* private Retain<SomeData> retain; // SomeDataは、ParcelableまたはSerializableであること
*
* @Override
* protected void onCreate(Bundle savedInstanceState) {
* retain = Retain.forSerializable()
* someData = retain.onCreate(savedInstanceState);
* if (someData == null) {
* someData = new SomeData()
* }
* }
*
* @Override
* protected void onSaveInstanceState(Bundle outState) {
* super.onSaveInstanceState(outState);
* retain.onSaveInstanceState(outState, someData);
* }
*
* @Override
* protected void onDestroy() {
* super.onDestroy();
* retain.onDestroy(this);
* }
*
* @param <T>
*/
public class Retain<T> {
static long sPrevId;
static final HashMap<Long, Object> sRetainData = new HashMap<>();
static final String RETAIN_ID_KEY = "retain_id_key";
static final String RETAIN_ID_VALUE = "retain_value_key";
public long mId;
private final boolean mIsSerializable;
private Retain(boolean isSerializable) {
this.mId = getNewId();
this.mIsSerializable = isSerializable;
}
static synchronized private long getNewId() {
long id = SystemClock.elapsedRealtime();
if (sPrevId == id) {
id++;
}
sPrevId = id;
return id;
}
static public<T extends Serializable> Retain<T> forSerializable() {
return new Retain<T>(true);
}
static public<T extends Parcelable> Retain<T> forParcelable() {
return new Retain<T>(false);
}
public boolean wasProcessKilled() {
return mWasProcessKilled;
}
public boolean wasRecreated() {
return mWasRecreated;
}
public boolean mWasProcessKilled = false;
public boolean mWasRecreated = false;
// 初回起動の時:
// nullを返す。
// wasRecreatedはfalse, wasProcessKilledはfalse
// 再生成でprocessが生きていた時:
// onSaveInstanceState()で渡されたTの参照を返す。
// wasRecreatedはtrue。wasProcessKilledはfalse。
// 再生成でprocessが死んでいた時:
// onSaveInstanceState()で渡されたTを、bundleから再生成したものを返す。
// wasRecreatedはtrue。wasProcessKilledはtrue。
public T onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
mWasRecreated = false;
mWasProcessKilled = false;
return null;
} else {
mWasRecreated = true;
long id = savedInstanceState.getLong(RETAIN_ID_KEY, -1);
if (id != -1) {
mId = id;
// staticなところからretainデータを取得
// 取れたら、それを返す
try {
T data = (T) sRetainData.get(id);
if (data != null) {
return data;
}
} catch (ClassCastException e) {
// idは一意なので、違う型のオブジェクトが取れることは無いはず
throw new RuntimeException(e);
}
// idがbundleにあるのにstaticな領域にデータがないということは、processがkillされた場合
// bundleから情報を復帰させる。
mWasProcessKilled = true;
Object val = savedInstanceState.get(RETAIN_ID_VALUE);
if (val == null) {
// idとdataはonSaveInstanceで一緒に保存しているので、idがあるのにdataが無いことは無いはず
throw new RuntimeException("cannot find data in bundle");
} else if (mIsSerializable && val instanceof Serializable) {
// val.getClass() == T.class みたいなことをしたいのだが…
return (T)val;
} else if (!mIsSerializable && val instanceof Parcelable) {
return (T)val;
} else {
// 誰かが同じキーを使って、違うデータを入れた?
throw new RuntimeException("cannot find data in bundle");
}
} else {
// saveInstanceStateを呼んでいないのか?
throw new RuntimeException("you haven't called saveInstanceState?");
}
}
}
// bundleとstatic領域にデータを保存する。
//
// ここで渡したdataは、
// - processが生きている限り、onSaveInstanceState()以後に行った変更でも、onCreate()で取得できる。
// - processが死んだ場合には、onSaveInstanceState()でbundleに保存した時点の値が、onCreate()で取得できる。
public void onSaveInstanceState(Bundle bundle, T data) {
bundle.putLong(RETAIN_ID_KEY, mId);
if (mIsSerializable) {
bundle.putSerializable(RETAIN_ID_VALUE, (Serializable) data);
} else {
bundle.putParcelable(RETAIN_ID_VALUE, (Parcelable) data);
}
sRetainData.put(mId, data);
}
public void onDestroy(Activity activity) {
if (activity != null && activity.isFinishing()) {
sRetainData.remove(mId);
}
}
@TargetApi(11)
public void onDestroy(android.app.Fragment f) {
if (f != null && f.isRemoving()) {
sRetainData.remove(mId);
}
}
public void onDestroy(android.support.v4.app.Fragment f) {
if (f != null && (f.isRemoving() || f.getParentFragment() != null && f.getParentFragment().isRemoving())) {
sRetainData.remove(mId);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment