本篇博客分享——备忘录模式。 它是一种行为模式,用于保存对象的当前状态,并且在之后的某个时间点,该对象可以恢复到之前的状态,也就是说:我后悔了,我想回到以前的状态。可以,这时候“备忘录模式”可以给你后悔的机会。
备忘录模式的定义
在不破坏对象封闭的前提下,捕获到对象的当前状态,并且在该对象之外保存这个状态,以便于以后该对象恢复到该状态。
备忘录模式的使用场景
- 需要保存对象在某一时刻的状态和部分状态
- 不希望外部直接访问其内部状态,通过中间对象,间接访问该对象的内部状态
备忘录模式的角色
- Originator:对象,可以理解为备忘录的主角。备忘录要存储的状态就是这个对象的内部状态。
- Memento:备忘录角色。 存储Originator的内部状态。Originator以外的对象不能访问Memento。
- Caretaker:备忘录角色的管理者。他主要负责存储备忘录,但是自己又不能访问备忘录的数据进行操作和访问。只能够将备忘录传递给其他角色。
示例
经常打游戏,这个示例就以闯关打游戏为例,我们当前第一关,血量是100, 然后闯过一关,是第二关,血量是90,但是呢,此时我不想玩了,下次重启游戏,要恢复到第二关,血量90的状态。
/**
* FileName:Originator
* Create By:liumengqiang
* Description:需要保存数据的对象
*/
public class Originator {
private static final String TAG = Originator.class.getSimpleName();
private int checkPoint = 1; //关卡
private int lifeValue = 100; //生命值
/**
* 玩游戏
*/
public void play() {
Log.e(TAG, String.format("当前%d关: ", checkPoint) + String.format(", 当前生命值:%d", lifeValue));
checkPoint ++;
lifeValue -= 10;
Log.e(TAG, String.format("到达%d关: ", checkPoint) + String.format(", 当前生命值:%d", lifeValue));
}
/**
* 创建备忘录
*/
private Memento storeStatus() {
Memento memento = new Memento();
memento.checkPoint = checkPoint;
memento.lifeValue = lifeValue;
return memento;
}
/**
* 退出游戏
*/
public Memento quit() {
return this.storeStatus();
}
/**
* 恢复状态
*/
public void restoreStatus(Memento memento) {
this.checkPoint = memento.checkPoint;
this.lifeValue = memento.lifeValue;
Log.e(TAG, String.format("恢复游戏后,当前闯关等级:%d,当前生命值:%d", checkPoint, lifeValue));
}
}
/**
* FileName:Memento
* Create By:liumengqiang
* Description:备忘录角色,除了Originator之外的对象都不能够操作该对象
*/
public class Memento {
public int checkPoint = 1; //关卡
public int lifeValue = 100; //生命值
}
/**
* FileName:Caretaker
* Create By:liumengqiang
* Description:负责管理备忘录,但是自己不能操作备忘录
*/
public class Caretaker {
private Memento memento;
public void createMemento(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
}
Originator originator = new Originator();
//开始玩游戏
originator.play();
//
Caretaker caretaker = new Caretaker();
//保存数据,并退出游戏
caretaker.createMemento(originator.quit());
//再一次开始玩游戏
Originator newOriginator = new Originator();
//恢复数据
newOriginator.restoreStatus(caretaker.getMemento());
上述代码比较简单,代码量也不大,不再多说,
自己可以实现一个笔记本:save,control+Z, control+ Y,三个功能。
Android 源码备忘录模式的实现
在开发中,我们经常遇到这种场景,当我们的APP切换的后台运行,但是由于各种原因,被系统杀死了,那数据也就不存在了,google给我们提供了onSaveInstanceState和onRestoreInstanceState方法,让我们在我们的APP在可能被系统杀死之前保存数据,并且在合适的时候恢复数据。 注意了,这里是:可能被系统杀死之前。
结合本片博客分享的设计模式——备忘录模式,是不是有点像? 也就是说,将Activity的数据对象保存在bundle中,并且在合适的时候,将bundle返回给Activity,使其恢复之前的数据。
那么具体的源码是怎么实现的呢?
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
// 保存Window的View视图状态
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
//保存Fragment状态
Parcelable p = mFragments.saveAllState();
....
//是否在Application中设置了ActivityLifeCallbacks
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
实际上重点是第一句话,将视图树存放在Bundle中,当之后在恢复视图树的时候,直接从Bundle中去数据就行了,以此来保证UI的一致性。Window的真正是是实现类是PhoneWindow,进入到PhoneWindow的saveHierarchyState方法看下:
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
//这里的mContentParent就是我们setContentView的View布局
if (mContentParent == null) {
return outState;
}
//
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
// 调用了我们布局ViewGroup的saveHierarchyState方法。
mContentParent.saveHierarchyState(states);
//把最终的视图树塞进Bundle中
outState.putSparseParcelableArray(VIEWS_TAG, states);
// Save the focused view ID.
//获取当前视图有焦点ID的View
final View focusedView = mContentParent.findFocus();
if (focusedView != null && focusedView.getId() != View.NO_ID) {
//保存当前焦点ID的View,Key:ID, value:View
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
}
......
//保存ActionBar的状态
if (mDecorContentParent != null) {
SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}
return outState;
}
上面的代码总结做了三件事:
- 将视图树的相关信息保存在集合中。
- 保存当前获取到焦点的View
- 保存ActionBar的状态
看过setContentView的小伙伴应该是知道的,这里的mContentParent实际上就是我们的自己的Activity的布局View,调用了我们设置的布局ViewGroup的saveHierarchyState方法,其实,不管我们的布局是LinearLayout还是FrameLayout,都没有重写saveHierarchyState方法,都是在超级父类View中定义实现的:
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
这里仅仅就一句话:调用了dispatchSaveInstanceState方法,那么这个方法最终是它的子类ViewGroup重写了:
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
final int count = mChildrenCount;
final View[] children = mChildren;
//遍历所有的子View
for (int i = 0; i < count; i++) {
View c = children[i];
if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
//执行每个子View的dispatchSaveInstanceState方法
c.dispatchSaveInstanceState(container);
}
}
}
主要是循环遍历子View,如果子View是ViewGroup, 那么继续递归循环,知道子 View是不是继承自ViewGroup的View为止。也就是说:最终要进入的方法是View的dispatchSaveInstanceState方法:
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
//这个View是否在XML中设置了ID,如果设置了ID,就执行onSaveInstanceState方法
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
//执行View的onSaveInstanceState方法
Parcelable state = onSaveInstanceState();
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
if (state != null) {
// Log.i("View", "Freezing #" + Integer.toHexString(mID)
// + ": " + state);
//这里将要保存的数据添加到在PhoneWindow中创建的集合中。
container.put(mID, state);
}
}
}
实际上这里最外层的判断就是是否给View设置了ID,如果没设置ID,那么就不会保存View的状态,如果设置了ID,那么执行onSavenInstanceState方法,向TextView,EditText的子View都重写了该方法,然后保存该View的相关数据。都保存在了PhoneWindow中创建的那个集合SparseArray中,然后将SparseArray塞入到了Bundle中,保存起来。
上面我们分析了Activity中onSaveInstanceState一旦调用,内部进行的相关流程操作,那么有个重要的问题那就是:Activity中的onSaveInstanceState方法在哪里回调执行的呢?
实际上这个问题猜也能猜出来,关于Activity生命周期的问题,基本都在ActivityThread中能够找的答案。
private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
......
callActivityOnStop(r, saveState, reason);
}
private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
.....
//
callActivityOnSaveInstanceState(r);
.....
//执行Activity的onStop方法。
r.activity.performStop(false /*preserveWindow*/, reason);
.......
}
private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
......
//实际上还是调用的Instrumentation的方法;
mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
......
}
进入到Instrumentation的callActivityOnSaveInstanceState方法中,一探究竟:
public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
activity.performSaveInstanceState(outState);
}
额,里面执行的是Activity的performSaveInstanceState方法。
final void performSaveInstanceState(Bundle outState) {
//执行的是onSaveInstanceState方法
onSaveInstanceState(outState);
......
}
最终执行到了onSaveInstanceState方法。
从头到尾捋一遍思路:首先当进程可能被杀死的时候,会从AMS发来消息,最终调用Activity的onSaveInstanceState方法,然后交给PhoneWindow保存视图树的相关数据信息,然后我们自己也可以在onSaveInstanceState方法中保存自己的数据。
至于onRestoreInstanceState方法是恢复数据的,其源码流程和onSaveInstanceState是对应的。
在这个示例中,Activity就可以看作是:Original;Bundle可以看作是:Memento;AMS可以看作是:Caretaker;
上述就是分享的备忘录 模式,纯手打,如果错误,欢迎指正,共同学习进步。