1. 备忘录设计模式介绍
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象恢复到原先保存的状态。
2. 备忘录设计模式使用场景
- 需要保存一个对象在某一个时刻的状态或部分状态。
- 如果用一个接口来让其它对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。
3. 备忘录设计模式UML类图
UML类图角色介绍
Originator:负责创建一个备忘录,可以记录、恢复自身的内部状态。同时Originator可以根据需要决定Memento存储自身的那些内部状态。
Memento: 备忘录角色,用于存储Originator的内部状态,并且可以防止Originator以外的对象访问Memento。
Caretaker: 负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其它对象。
4. 备忘录设计模式的简单实现
情景如下:我们都玩过单机游戏,单机里面有一个很重要的功能就是存档,存储当前游戏进度。下次再进入游戏时,恢复上一次的进度,继续游戏。
- (1)、游戏类:Game
public class Game {
private int mCheckpoint = 1; //分数
private int mLifeValue = 100; //生命
//玩游戏
public void play() {
mLifeValue -= 10;
mCheckpoint++;
}
public void quit() {
System.out.println("退出游戏");
}
//显示当前游戏信息
public void showInfos() {
System.out.println(this.toString());
}
public Memoto createMemoto() {
//创建存档
Memoto memoto = new Memoto();
memoto.mLifeValue = mLifeValue;
memoto.mChackpoint = mCheckpoint;
return memoto;
}
//恢复存档
public void restoreMemoto(Memoto memoto) {
this.mCheckpoint = memoto.mChackpoint;
this.mLifeValue = memoto.mLifeValue;
System.out.println("恢复进度");
}
@Override
public String toString() {
return "当前生命值:" + mLifeValue + ",分数:" + mCheckpoint;
}
}
上面的代码,最终要的部分就是createMemoto()方法,将当前状态信息存储在Memoto对象里面。
- (2)、备忘录类:
public class Memoto {
public int mChackpoint;
public int mLifeValue;
public String mWeapon;
}
备忘录类用来存储游戏类里面一个或至多个信息。
- (3)、备忘录操作者:Caretaker角色:
public class Caretaker {
Memoto memoto;//备忘录
public void archive(Memoto memoto) {
this.memoto = memoto;
}
//获取存档
public Memoto getMemoto() {
return memoto;
}
}
该类的作用就是操作备忘录类Memoto本身的,并不对备忘录里面的信息读取操作。
- (4)、测试类:
public class Client {
public static void main(String[] args) {
Game game = new Game();
//打游戏
game.play();
//存档
Caretaker caretaker = new Caretaker();
caretaker.archive(game.createMemoto());
game.showInfos();
//退出游戏
game.quit();
System.out.println("-----");
//恢复游戏
Game newGame = new Game();
newGame.restoreMemoto(caretaker.getMemoto());
//显示当前游戏信息
newGame.showInfos();
}
}
上面的测试类就是创建一个游戏类,接着游戏类修改自身的属性,接着游戏类创建备份,然后新建游戏类,恢复备份,显示游戏进度和之前的游戏对象进度一模一样。
5. 备忘录设计模式在Android源码中
在Android源码中,状态模式的应用表现在Activity的状态保存,在onSaveIinstanceState和onRestoreInstanceState方法中
当Activity不是正常方式退出,且Activity在随后的时间内被系统杀死之前会调用这两个方法让开发人员可以有机会存储Activity相关信息,并且下次再返回式恢复这些数据。
首先我们来说下onSaveInstanceState()方法里面干了什么事:
这是onSaveInstanceState()里面的方法,
protected void onSaveInstanceState(Bundle outState) {
//1.存储窗口的视图树状态
outState.putBundle(WINDOW_HIERARCHY_TAG,
mWindow.saveHierarchyState());
//2.存储Fragment的状态
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
//3.调用Activity的ActivityLifecycleCallbacks的onSaveInstanceState函数进行状态存储
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
- (1)、存储窗口的视图树状态
- (2)、存储Fragment的状态
- (3)、调用Activity的ActivityLifecycleCallbacks的onSaveInstanceState函数进行状态存储
上面三个步骤都是存储,我们首先来分析下存储窗口的视图状态:
mWindow.saveHierarchyState());
这句代码就是存储窗口的视图树状态,这里的mwindow对象是PhoneWindow ,我们在PhoneWindow找到这个方法:
saveHierachyState()方法简化如下:
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
//存储整颗视图树的结构
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
mContentParent.saveHierarchyState(states);
outState.putSparseParcelableArray(VIEWS_TAG, states);
// 保存当前获取了焦点的View
//存储整个面板的状态
//存储ActionBar的状态
return outState;
}
在以上saveHierarchyState函数中,主要存储了与当前UI、ActionBar相关的View状态。
这里我们分析存储整颗视图树:
- 代码中的mContentParent就是我们通过Activity的setContentView设置的内容视图,它是整个视图树的根节点。
- mContentParent是一个ViewGroup对象,我们在ViewGroup的父类View中到saveHierarchyState()方法:
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
//如果View没有设置Id,那么该View的状态信息将不会被存储
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
//调用那个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);
//存入View状态信息,key为view的id。
container.put(mID, state);
}
}
}
上面的代码意思大致如下:
- 如果View没有设置id,那么该View的状态信息将不会被保存
- 调用onSaveInsttanceState()方法获取自身状态信息。
- 当前View的id为key,状态信息为value,存入之前创建的SparseArray 中,本质上是一个Object数组。
以上是View类的中saveHierarchySate函数中dispatchSaveInstanceState函数来存储自身的状态
如果是ViewGroup呢?下面是ViewGroup中的dispatchSaveInstanceState函数:
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
//调用父类View的dispatchInstanceState方法存储自身状态
super.dispatchSaveInstanceState(container);
final int count = mChildrenCount;
final View[] children = mChildren;
//遍历所有的子视图调用其dispatchInstanceState方法存储它们的状态
for (int i = 0; i < count; i++) {
View c = children[i];
if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
c.dispatchSaveInstanceState(container);
}
}
}
上面的代码做了以下两件事:
- 首先调用父类View的dispatchSaveInstanceState方法存储了自身的状态信息
- 接着遍历所有的子类,调用其dispatchSaveInstanceState方法存储它们的状态信息
在View的saveHierarchyState方法里面有如下代码:
//调用那个onSaveInstanceState()方法获取自身状态信息
Parcelable state = onSaveInstanceState();
这句代码的意思是获取自身状态信息,我们点进去查看源代码如下:
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (mStartActivityRequestWho != null) {
BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
return state;
}
return BaseSavedState.EMPTY_STATE;
}
上面的代码意思是:返回一个存储了当前View状态信息的Parcelable对象,如果没有任何信息,返回null,默认返回null。我们可以得出以下信息
- View视图的状态信息的存储在Parcelable对象里面
- 如果我们要保存View的状态信息,需要覆写onSaveInstanceState()方法,将需要保存的信息存放在Parcelable里面,然后返回。
到这里我们画张图小结一下View和ViewGroup存储的流程:
上面分析了存储了Window的视图树状态信息。
存储了Window的视图树状态信息后,便会执行存储Fragment中的状态信息、回退栈等 。
下面我们来分析存储了状态信息的Bundle数据存储在哪里?
我们知道onSveInstanceState是在Activity被销毁之前,onStop调用之前。onStop方法在ActivityThread的performStopActivity函数中,这里就不列出代码了,主要的步骤大致如下:
- (1)、判断是否需要存储Activity
- (2)、如果需要存储Activity状态,调用onSaveInstanceState函数获取状态信息。
- (3)、将状态信息存储到ActivityClientRecord对象的state字段
- (3)、系统维护了一个Acitivity信息表mActivities,将AcitivityClientRecord对象存储到Acitivity信息表中。
- (4)、调用Activity的onStop()函数
当Activity重新启时:
- 从mActivities查询对应的ActivityClientRecord,如果这个记录对象中包含有状态信息,那么调用Activity的onRestoreInstanceState函数,然后将这些状态信息传递给onCreat方法
总结一下onSaveInstanceState的调用时机:
当系统未经我们允许时销毁了Acitivity,onSaveInstanceState()方法会被调用。常见的几种场景:
- 当用户按下Home键时
- 按下电源键时
- 启动一个新的Activity时
- 来电话时
- 屏幕发生旋转时
6. 备忘录设计模式在Android开发中
如下场景:简单实现上面的onSaveInstanceState()的使用,当我我们输入用户名和密码后,通过旋转屏幕Activity会被销毁,如果不存储,相关信息可能会丢失,所以在onSaveInstanceState保存相关信息,在onCreate方法里面获取信息,重新填充即可。
简单演示:
代码简单实现如下:
public class MainActivity extends AppCompatActivity {
private EditText et_name;
private EditText et_psw;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_name = (EditText) findViewById(R.id.et_name);
et_psw = (EditText) findViewById(R.id.et_psw);
if (savedInstanceState!=null){
String username = savedInstanceState.getString("username");
String psw = savedInstanceState.getString("psw");
System.out.println("username:" + username + ",psw:" + psw);
et_name.setText(username);
et_psw.setText(psw);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("username", et_name.getText().toString());
outState.putString("psw", et_psw.getText().toString());
super.onSaveInstanceState(outState);
}
}
7、总结
- 优点:
- 给用户提供了一种可以恢复状态的机制。可以是用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装。使得用户不需要关心状态的保存细节。
- 缺点:
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。