1. 备忘录模式的概念
备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
备忘录模式就像游戏中的存档,在某个时候我们想要回退到之前状态是,使用存档就可以了。备忘录提供了这么一套代码的思想。
它是一种行为模式。用于保存当前的状态,并且在之后可以再次恢复到此状态。
这种看似在程序中很少出现的用法,我们是否有机会使用到呢?
2. UML图和模板代码
我们来看下备忘录模式的UML图:
上面一共有三个对象,他们的作用和关联是:
-
Originator
(发起人)
负责创建一个备忘录Memento,可以记录、恢复自身的内部状态。同时Originator还可以根据需要决定Memento存储自身的哪些内部状态。 -
Memento
(备忘录)
用于存储Originator的状态,并且可以防止Originator以外的对象访问Memento -
Caretaker
负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。
下面是模板代码,我们首先需要保存的数据源类:
public class Originator {
// 需要保存的属性, 可以有多个
private String state;
// 创建备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
public Memento createMemento(){
return new Memento(state);
}
public void setMemento(Memento memento){
state = memento.getState();
}
public String getState() {
return state;
}
// 恢复状态
public void setState(String state) {
this.state = state;
}
@Override
public String toString() {
return "Originator{" +
"state='" + state + '\'' +
'}';
}
}
它就是我们平时使用的类,它可以做很多事,在这个模式中,它多了一套代码,就是用来创建和设置Memento对象。
我们来创建一个备忘录类,来存放发起类想要存放的状态:
public class Memento {
private String state;
// 将需要保存的数据导入
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
最后是 Caretaker,它和备忘录类是聚合关系,它里面可以维护一份备忘录类列表,用于做整体的操作,相当于我们常用Manager类:
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
最后看看客户端中的使用:
public static void main(String[] args) {
Originator o = new Originator();
o.setState("On"); // Originator初始状态,状态为“On”
System.out.println(o);
Caretaker c = new Caretaker();
c.setMemento(o.createMemento()); // 保存状态时,由于有了很好的封装,可以隐藏Originator的实现细节
o.setState("Off"); // 将Originator的状态改为 “Off”
System.out.println(o);
o.setMemento(c.getMemento());
System.out.println(o); // 恢复初始状态
}
可以看到,我们把保存状态就写成了一句话:c.setMemento(o.createMemento());
这样做的好处就是:把要保存的细节给封装在了Memento中了,哪一天要更改保存的细节也不用影响客户端了。
所以我们可以知道备忘录模式的使用场景:
- Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性的一小部分时,Originator可以根据保存的Memto信息还原到前一状态。
很明显,就是用于撤销/恢复 的场景,当然了,撤销/恢复的实现我们可能早就会实现了,就是使用两个栈(一个撤销栈、一个恢复栈)用来保存状态。
而在备忘录模式中,相当于把两个栈放在了 Caretaker
中。
3. Android中备忘录模式的应用
Android中也有使用到备忘录模式的地方,而且我们都很熟知,那就是 Activity.onSaveInstanceState
和 Activity.onRestoreInstanceState
。
当Activity不是正常方式退出,且Activity在随后的时间内被系统杀死之前会调用,并且在下次返回Activity时恢复这些数据。
通过这两个函数,开发人员能够在某些特殊场景下存储于界面相关的信息,提升用户体验。
例如,用户在写了一大段信息之后,此时一个电话打了进来,用户的短信输入界面退到后台,如果在打电话的过程中短信应用被系统杀死了,那么在用户再次进入信息界面时,上一次输入的内容将会不复存在。
Activity的状态存储机制正是为了应对这情况出现的。那么它们的原理是怎样的呢?我们还是来看看源码:
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); // 1
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p); // 2
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
getApplication().dispatchActivitySaveInstanceState(this, outState); // 3
}
注释1: 存储窗口的视图树的状态
注释2: 存储Fragment的状态
注释3: 调用Activity的 ActivityLifecycleCallbacks.onSaveInstanceState()
,如果说用户在Application类设置了lifecycle的话。
这里就不细细深入源码了,我们只用知道,在执行了第一步后,会从根View开始递归到子View将带有id的视图存放起来。
我们知道AMS层管理者Activity的生命周期和信息的存储,每个Activity在AMS会存放一个 ActivityClientRecord
,AMS会有一个列表维护他们。而 onSaveInstanceState()
会在页面销毁前,准确的说是 onStop()
前,将视图状态等信息存放到该ActivityClientRecord的state
成员变量中。
在Activity的启动流程中,如果AMS发现该Activity的 ActivityClientRecord.state
不为空,则说明Activity是调用过onSaveInstanceState方法来存储信息的,那么在调用了 Activity的onCreate()
后,就会调用 onRestoreInstanceState()
来讲数据渲染在Activity上。
这就是Android体现备忘录模式的地方,来看看这些类的关系
- originator发起者
就是Activity、Fragment、ViewGroup这些需要保存状态的类 - momento备忘录
就是存放状态的地方,在上面的例子中,备忘录显然是 Bundle类。它通过序列化存储了数据 - Caretaker
显然管理备忘录的就是ActivityManagerService
这个大总管。无论是onSaveInstanceState()
还是onRestoreInstanceState()
都是他调用的。
最后,补一下 onSaveInstanceState()
的调用时机,因为这个方法不是一定会调用的,它会出现在下面这些场景中:
- 按Home键
因为系统不知道在按下Home键后要去运行多少个其他程序,所以这里会调用这个方法来提供开发人员存储信息 - 长按Home
- 按下电源键(关闭屏幕)
- 从一个Activity中启动一个新的Activity时
- 屏幕方向切换,从竖屏切换到横屏时
- 电话打入等情况时
总而言之就是,不是用户主动退出某个Activity或者跳转到其他Activity的情况下就会触发 onSaveInstanceState()
。
4. 总结
来总结下备忘录模式的使用场景:
- 需要保存一个对象在某一个时刻的状态或部分状态。
- 如果用一个接口来让其他对象得到这个状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。
在Android中, onSaveInstanceState()
和 onRestoreInstanceState()
体现了备忘录模式,用于恢复Activity在不正常死亡下的状态恢复。
该模式的优点有:
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便的回到某个历史状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点有:
- 因为要存储状态,所以必须要进行数据的拷贝,如果要存储的状态比较多,那么因为拷贝就会消耗很多资源,这势必会产生内存的耗费。