行为型设计模式之备忘录模式

备忘录模式的定义

备忘录模式是一种行为型设计模式,该模式用于保存对象当前的状态,并且在之后可以再次恢复到此状态。

实现效果为:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便后续将对象恢复到原来的状态。

备忘录模式的使用场景

  1. 需要保存一个对象在某一个时刻的状态或部分状态
  2. 一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。(如果使用接口来让其他对象获取对象的状态,会破坏封装性)

备忘录模式的UML类图

memoto pattern

三个角色:

  • Originator:需要保存状态的对象。负责创建一个备忘录,可以记录、恢复自身的内部状态。同时 Originator 还可以根据需要决定 Memento 存储自身的哪些内部状态。
  • Memento(类似于 pojo 类)备忘录角色。用于存储 Originator 内部状态,并且可以防止 Originator 以外的对象访问 Memento
  • Caretaker:负责存储备忘录,不能对备忘录的内容进行操作和访问,只能将备忘录传递给其他对象。

Android源码中的备忘录模式

日常开发中如果需要保存什么数据以防止 Activity 意外销毁,第一时间会想到 Activity 中的这两个方法——onSaveInstanceStateonRestoreInstanceState。其内部具体是如何实现数据保存的呢?

先透漏一下,这里面使用到了备忘录模式。

1
2
3
4
5
6
7
8
9
10
11
12
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,
//则调用 ActivityLifeCycleCallbacks 的 onSaveInstanceState 进行存储状态。
getApplication().dispatchActivitySaveInstanceState(this, outState);
}

Activity.onSaveInstanceState 方法中主要做了三件事

  1. 存储窗口的视图树的状态
  2. 存储 Fragment 中的状态
  3. 若用户设置了 Activity 的 ActivityLifeCycleCallbacks,则调用 ActivityLifeCycleCallbacks 的 onSaveInstanceState 进行存储状态。

首先看看步骤 1,该步骤将 Window 对象中的视图树中的各个 View 状态存储到 Bundle 中。

Window 的具体实现在 PhoneWindow 中.以下为 PhoneWindow.saveHierarchyState 的具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
// SparseArray 相当于一个 key 为 整型的 map
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
//此处的 mContentParent 就是我们 setContentView 时设置的 View
mContentParent.saveHierarchyState(states);
outState.putSparseParcelableArray(VIEWS_TAG, states);
// 持有焦点的 View 必须设置 id,否则重新进入该界面时不会恢复它的焦点状态
final View focusedView = mContentParent.findFocus();
if (focusedView != null && focusedView.getId() != View.NO_ID) {
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
}
// 存储整个面板的状态
SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
savePanelState(panelStates);
if (panelStates.size() > 0) {
outState.putSparseParcelableArray(PANELS_TAG, panelStates);
}
// 保存 actionbar 的状态
if (mDecorContentParent != null) {
SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}
return outState;
}

看看步骤 2 中的 mContentParent.saveHierarchyState 方法,mContentParent 是一个 ViewGroup 但是 saveHierarchyState 方法并不是定义在 ViewGroup 中,而是定义在它的父类——View 中,查看下该方法在 View 中的实现。

1
2
3
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
//1. 只有含有 id 的 View,状态才会被存储
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
//2. 调用 onSaveInstanceState 方法获取自身状态
Parcelable state = onSaveInstanceState();
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
//3. 将自身状态存放到 container 中
if (state != null) {
container.put(mID, state);
}
}
}

View.onSaveInstanceState 方法默认存储的状态为空状态。但是它的子类通常都有定义自身的覆盖方法。

1
2
3
4
5
6
7
8
9
10
@CallSuper
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;
}

步骤 2 中的 View 的调用大致如下:saveHierarchyState ==》 dispatchSaveInstanceState ==》 onSaveInstanceState

  • 其中要注意的是 只有含有 id 的 View,状态才会被存储,如果没有给 view 赋一个 id,那么系统是不会帮忙保存该 view 的状态的。

View 类中的 saveHierarchyState 方法调用了dispatchSaveInstanceState 方法用来存储自身状态。 ViewGroup 覆写了 dispatchSaveInstanceState 来存储自身以及子视图的状态。

ViewGroup.dispatchSaveInstanceState具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {//遍历调用子 View 的 dispatchSaveInstanceState 方法
View c = children[i];
if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
c.dispatchSaveInstanceState(container);
}
}
}

可以看到 ViewGroup 的 dispatchSaveInstanceState 方法会先调用 super.dispatchSaveInstanceState(container); 存储自身的状态。然后遍历调用所有子视图的 dispatchSaveInstanceState(container) 方法来保存它们的状态,如果子 View 也是一个 ViewGroup,则会再次执行这个过程。


我们以 TextView 的 saveInstanceState 方法为例,看看具体的控件是如何保存自身状态的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
final boolean freezesText = getFreezesText();
boolean hasSelection = false;
int start = -1;
int end = -1;
//存储 TextView 的 start、end
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// 是否存存在选项
hasSelection = true;
}
}
if (freezesText || hasSelection) {
SavedState ss = new SavedState(superState);
//保存 TextView 的文本内容
if (freezesText) {
if (mText instanceof Spanned) {
final Spannable sp = new SpannableStringBuilder(mText);
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}
ss.text = sp;
} else {
ss.text = mText.toString();
}
}
//存储 TextView 的 start、end
if (hasSelection) {
// XXX Should also save the current scroll position!
ss.selStart = start;
ss.selEnd = end;
}
if (isFocused() && start >= 0 && end >= 0) {
ss.frozenWithFocus = true;
}
ss.error = getError();
if (mEditor != null) {
ss.editorState = mEditor.saveInstanceState();
}
//返回状态对象
return ss;
}
return superState;
}

调用 View 的 onSaveInstance 函数之后就得到了 View 要存储的数据,此时执行到 View 的 dispatchSaveInstanceState 方法中的注释 3。这里以 View 的 id 为 key,以状态为 value,存储到 container( SparseArray 类型)中。

1
2
3
4
//3. 将自身状态存放到 container 中
if (state != null) {
container.put(mID, state);
}

  • 存储完 Window 的视图状态信息之后,便会执行存储 Fragment 中的状态信息、回退栈等。Fragment 也是通过调用自身的 onSaveInstaceState 方法来存储自身的 View 视图树状态的。
  • 最后就是调用用户设置的 ActivityLifecycleCallbacks 的 onSaveInstaceState 方法,让用户做一些额外的处理

前面我们所提及到的只是备忘录模式中的 Originator 角色,即需要保存备忘录的对象。不过也有涉及到 CareTaker 角色——Activity。下面我们再看看另外两个角色——Memoto 和 CareTaker。

存了状态信息的 Bundle 数据存储在哪?

  • onSaveInstance 方法是在 onStop 方法之前调用的。Activity.onStop 方法是通过 Activity 的 performStopActivity 间接调用。
    1
    2
    3
    4
    5
    6
    final void performStopActivity(IBinder token, boolean saveState, String reason) {
    //获取 ActivityClientRecord
    ActivityClientRecord r = mActivities.get(token);
    // saveState 表示是否保存状态
    performStopActivityInner(r, null, false, saveState, reason);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void performStopActivityInner(ActivityClientRecord r,
StopInfo info, boolean keepShown, boolean saveState, String reason) {
if (r != null) {
if (!keepShown && r.stopped) {
if (r.activity.mFinished) {
// 如果正在执行销毁过程,是用户主动销毁。 activity 不打算恢复,我们也没必要调用 onStop 方法
return;
}
//代码省略
}
// 在调用 onStop 之前必须先调用 onPause
performPauseActivityIfNeeded(r, reason);
//代码省略
// 接下来让 activity 保存它目前的状态和它所管理的 dialogs
if (!r.activity.mFinished && saveState) {
if (r.state == null) {
// 间接调用 Activity.onSaveInstance()
callCallActivityOnSaveInstanceState(r);
}
}
if (!keepShown) {
try {
// 执行 onStop 方法
r.activity.performStop(false /*preserveWindow*/);
} catch (Exception e) {
// ...
}
//将 stop 字段置为 true 表示已经调用了 stop 方法。
r.stopped = true;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) {
r.state = new Bundle();//内容就存储在该 Bundle 中
r.state.setAllowFds(false);
if (r.isPersistable()) {
r.persistentState = new PersistableBundle();
mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
r.persistentState);
} else {
//该方法实际调用了 Activity.onSaveInstanceState 方法
mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
}
}
1
2
3
4
public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
PersistableBundle outPersistentState) {
activity.performSaveInstanceState(outState, outPersistentState);
}

上面的 performStopActivity 与 performStopActivityInner 方法中,首先通过 token 从 mActivities 中获取一个 ActivityClientRecord 对象,状态信息就是存储在这里面的。获取该对象之后,调用了 performStopActivityInner 方法,对于保存状态而言,该方法大概有如下三步

  1. 判断 Activity 是否需要保存状态
  2. 如果需要,则调用 onSaveInstance 方法,该方法会将状态信息存储到 ActivityClientRecord 中
  3. 调用 Activity.onStop() 方法

执行 onStop 方法之前,系统会根据情况来选择是否存储 Activity 的状态,并且将这些状态(简介地)存储到 mActivities 中。

mActivities 是一个 ArrayMap<IBinder, ActivityClientRecord> ,它维护了一个 Activity 的信息表,当 Activity 重新启动时,会从 mActivities 中查询对应的 ActivityClientRecord,如果这个记录对象中含有状态信息就调用 Activity 的 onRestoreInstanceState 方法。开发人员可以从这个方法中做一些状态恢复操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//代码省略
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//1. 构建 Activity
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
//代码省略
} catch (Exception e) {
//代码省略
}
try {
//2. 创建一个 Application 对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
//代码省略
if (activity != null) {
//创建 appContext,类型为 ContextImpl
Context appContext = createBaseContextForActivity(r, activity);
//代码省略
//3. 关联 appContext、Application 等对象到 Activity 中
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
//代码省略
activity.mCalled = false;
//4. 调用 Activity.onCreate 方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
//代码省略
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
//调用 onStart 方法
activity.performStart();
r.stopped = false;
}
//5. 如果有保存状态的话,调用 Activity.onRestoreInstanceState 方法恢复状态
if (!r.activity.mFinished) {
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
//代码省略
}
r.paused = true;
//6. 将 Activity 的信息记录对象——ActivityClientRecord 存储到 mActivities 中。
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
}
return activity;
}

在上面的注释 5 处,系统会判断 Activity 是否调用过了 Activity.finish() 方法、是否是「永久的」以及 ActivityClientRecord 对象中的 state 是否为空,

  • 如果满足条件,就会获取存储的状态信息传递给 Activity.onRestoreInstanceState 方法,也会将这些数据传递给 onCreate 方法的 bundle 参数 。
  • 不过 Google 官方推荐调用 onRestoreInstanceState 方法来恢复状态,因为只有在存储有状态信息的时候才会调用该方法,而在 onCreate 方法中还需要先进行判空处理。

小结

上述过程中备忘录模式的三种关键角色分别由什么类扮演?

  • CareTaker:Activity 负责存储、恢复 UI 的态信息。
  • Originator:Activity、Fragment、View、ViewGroup ,是需要存储状态的对象
  • Memoto:由 Bundle 类扮演
  • Activity 会在停止之前根据 Activity 的退出情景来选择是否需要存储状态
  • 重新启动该 Activity 时会判断 ActivityClientRecord 对象中是否存储了 Activity 的状态
    • 如果含有状态,调用 Activity.onRestoreInstanceState() 方法恢复状态。从而使得 Activity 的 UI 可以恢复至异常退出前的状态。

你可能会问的问题

onSaveInstanceState 何时被调用

onSaveInstanceState() 方法会在什么时候被执行?
有这么几种情况:

  1. 当用户按下 HOME 键时。
    • 这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
  2. 长按HOME键,选择运行其他的程序时。
  3. 按下电源按键(关闭屏幕显示)时。
  4. 从 Activity A 中启动一个新的 Activity 时。
  5. 屏幕方向切换时,例如从竖屏切换到横屏时。

onSaveInstanceState 的调用在 onStop 方法之前,但是与 onPause 方法之间没有既定关系。

  • 总而言之,当系统存在「==未经用户许可==」时销毁了我们的 Activity,则 onSaveInstanceState() 会被系统调用,这是系统的责任,因此它必须提供一个机会让用户保存数据。
  • 「经用户许可」的情况不多,通常只有用户按下回退键这一种。这种情况下是用户主动退出某个 Activity,系统不会调用 onSaveInstanceState() 方法。

各个 「Originator 」 的 onSaveInstanceState() 方法的默认实现是怎么样的?

在前面的分析中我们知道即使没有覆写 onSaveInstanceState()方法, ViewGroup、View、Fragment、Activity 内部都有自己的默认实现,它们的默认实现也会保存某些状态数据

  • 比如 activity 中各种 UI 控件的状态。android 应用框架中定义的几乎所有 UI 控件都恰当的实现了 onSaveInstanceState() 方法,因此当 Activity 被销毁和重建时, 这些 UI 控件会自动保存和恢复状态数据.
    • EditText 控件会自动保存和恢复输入的数据
    • CheckBox 控件会自动保存和恢复选中状态
  • 开发者只需要为这些控件指定一个唯一的 id(通过设置 android:id 属性即可), 剩余的事情就可以自动完成了
    • 注意:如果没有为控件指定ID, 则这个控件就不会进行自动的数据保存和恢复操作。

由上所述, 如果我们需要覆写 onSaveInstanceState() 方法, 一般会在第一行代码中调用该方法的默认实现:super.onSaveInstanceState(outState)。

有默认实现,还需要重写 onSaveInstanceState() 方法吗?

既然该方法的默认实现可以自动的保存UI控件的状态数据, 那什么时候需要覆写该方法呢?

如果需要保存额外的数据时, 就需要覆写 onSaveInstanceState() 方法。大家需要注意的是:onSaveInstanceState()方法只适合保存瞬态数据, 比如 UI 控件的状态,成员变量的值等,而不应该用来保存持久化数据,持久化数据应该当用户离开当前的 activity时,在 onPause() 中保存(比如将数据保存到数据库或文件中)。说到这里,还要说一点的就是在onPause()中不适合用来保存比较费时的数据,所以这点要理解。

另外由于 onSaveInstanceState() 方法方法不一定会被调用, 因此不适合在该方法中保存持久化数据, 例如向数据库中插入记录等。 保存持久化数据的操作应该放在 onPause() 中。若是永久性值,则在 onPause() 中保存;若有大量要保存的数据,则另开线程,以免阻塞 UI 线程。

参考资料与学习资源推荐

Show Comments
0%