备忘录模式的定义
备忘录模式是一种行为型设计模式,该模式用于保存对象当前的状态,并且在之后可以再次恢复到此状态。
实现效果为:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便后续将对象恢复到原来的状态。
备忘录模式的使用场景
- 需要保存一个对象在某一个时刻的状态或部分状态
- 一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。(如果使用接口来让其他对象获取对象的状态,会破坏封装性)
备忘录模式的UML类图

三个角色:
- Originator:需要保存状态的对象。负责创建一个备忘录,可以记录、恢复自身的内部状态。同时 Originator 还可以根据需要决定 Memento 存储自身的哪些内部状态。
- Memento(类似于 pojo 类)备忘录角色。用于存储 Originator 内部状态,并且可以防止 Originator 以外的对象访问 Memento
- Caretaker:负责存储备忘录,不能对备忘录的内容进行操作和访问,只能将备忘录传递给其他对象。
Android源码中的备忘录模式
日常开发中如果需要保存什么数据以防止 Activity 意外销毁,第一时间会想到 Activity 中的这两个方法——onSaveInstanceState、onRestoreInstanceState。其内部具体是如何实现数据保存的呢?
先透漏一下,这里面使用到了备忘录模式。
|
|
Activity.onSaveInstanceState 方法中主要做了三件事
- 存储窗口的视图树的状态
- 存储 Fragment 中的状态
- 若用户设置了 Activity 的 ActivityLifeCycleCallbacks,则调用 ActivityLifeCycleCallbacks 的 onSaveInstanceState 进行存储状态。
首先看看步骤 1,该步骤将 Window 对象中的视图树中的各个 View 状态存储到 Bundle 中。
Window 的具体实现在 PhoneWindow 中.以下为 PhoneWindow.saveHierarchyState 的具体实现。
|
|
看看步骤 2 中的 mContentParent.saveHierarchyState 方法,mContentParent 是一个 ViewGroup 但是 saveHierarchyState 方法并不是定义在 ViewGroup 中,而是定义在它的父类——View 中,查看下该方法在 View 中的实现。
|
|
View.onSaveInstanceState 方法默认存储的状态为空状态。但是它的子类通常都有定义自身的覆盖方法。
步骤 2 中的 View 的调用大致如下:saveHierarchyState ==》 dispatchSaveInstanceState ==》 onSaveInstanceState
- 其中要注意的是 只有含有 id 的 View,状态才会被存储,如果没有给 view 赋一个 id,那么系统是不会帮忙保存该 view 的状态的。
View 类中的 saveHierarchyState 方法调用了dispatchSaveInstanceState 方法用来存储自身状态。 ViewGroup 覆写了 dispatchSaveInstanceState 来存储自身以及子视图的状态。
ViewGroup.dispatchSaveInstanceState具体实现如下:
可以看到 ViewGroup 的 dispatchSaveInstanceState 方法会先调用 super.dispatchSaveInstanceState(container); 存储自身的状态。然后遍历调用所有子视图的 dispatchSaveInstanceState(container) 方法来保存它们的状态,如果子 View 也是一个 ViewGroup,则会再次执行这个过程。
我们以 TextView 的 saveInstanceState 方法为例,看看具体的控件是如何保存自身状态的。
|
|
调用 View 的 onSaveInstance 函数之后就得到了 View 要存储的数据,此时执行到 View 的 dispatchSaveInstanceState 方法中的注释 3。这里以 View 的 id 为 key,以状态为 value,存储到 container( SparseArray 类型)中。
- 存储完 Window 的视图状态信息之后,便会执行存储 Fragment 中的状态信息、回退栈等。Fragment 也是通过调用自身的 onSaveInstaceState 方法来存储自身的 View 视图树状态的。
- 最后就是调用用户设置的 ActivityLifecycleCallbacks 的 onSaveInstaceState 方法,让用户做一些额外的处理
前面我们所提及到的只是备忘录模式中的 Originator 角色,即需要保存备忘录的对象。不过也有涉及到 CareTaker 角色——Activity。下面我们再看看另外两个角色——Memoto 和 CareTaker。
存了状态信息的 Bundle 数据存储在哪?
- onSaveInstance 方法是在 onStop 方法之前调用的。Activity.onStop 方法是通过 Activity 的 performStopActivity 间接调用。123456final void performStopActivity(IBinder token, boolean saveState, String reason) {//获取 ActivityClientRecordActivityClientRecord r = mActivities.get(token);// saveState 表示是否保存状态performStopActivityInner(r, null, false, saveState, reason);}
|
|
|
|
|
|
上面的 performStopActivity 与 performStopActivityInner 方法中,首先通过 token 从 mActivities 中获取一个 ActivityClientRecord 对象,状态信息就是存储在这里面的。获取该对象之后,调用了 performStopActivityInner 方法,对于保存状态而言,该方法大概有如下三步
- 判断 Activity 是否需要保存状态
- 如果需要,则调用 onSaveInstance 方法,该方法会将状态信息存储到 ActivityClientRecord 中
- 调用
Activity.onStop()方法
执行 onStop 方法之前,系统会根据情况来选择是否存储 Activity 的状态,并且将这些状态(简介地)存储到 mActivities 中。
mActivities 是一个 ArrayMap<IBinder, ActivityClientRecord> ,它维护了一个 Activity 的信息表,当 Activity 重新启动时,会从 mActivities 中查询对应的 ActivityClientRecord,如果这个记录对象中含有状态信息就调用 Activity 的 onRestoreInstanceState 方法。开发人员可以从这个方法中做一些状态恢复操作。
|
|
在上面的注释 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() 方法会在什么时候被执行?
有这么几种情况:
- 当用户按下 HOME 键时。
- 这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
- 长按HOME键,选择运行其他的程序时。
- 按下电源按键(关闭屏幕显示)时。
- 从 Activity A 中启动一个新的 Activity 时。
- 屏幕方向切换时,例如从竖屏切换到横屏时。
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 线程。
参考资料与学习资源推荐
- 《Android 源码设计模式解析与实战》
- Android 开发之 instanceState详解