View的onSaveInstanceState和onRestoreInstanceState过程分析
为什么要介绍这2个方法呢?这是因为在我们的开发中最近遇到了一个很诡异的bug。大体是这样的:在我们的ViewPager中
有2页的root view都是ScrollView,我们在xml里面都用了android:id="@+id/scroll_view"这样的代码,即2个布局里面的
ScrollView用了同一个id。我们重载了ScrollView的onSaveInstanceState()用来save当前的scrollX和scrollY,在使用过程中
发现restore回来的时候其中一个的scrollY总是不对并且好像等于另一个的scrollY。这让我们很是疑惑,最终我们的一个工程师发现
了问题所在,就是因为2个ScrollView用了同一个id,所以导致系统在save state的时候一个覆盖了另一个的结果。接下来的内容,我
们就重点来看看这个save的过程。当然了,可能有人会问我们为啥要自己save ScrollView的滚动位置呢,难道Android系统自己没做吗?
答案是,是的,至少可以说在各个版本的Android之间没做好,看眼源码:
@Override
protected Parcelable onSaveInstanceState() {
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Some old apps reused IDs in ways they shouldn't have.
// Don't break them, but they don't get scroll state restoration.
return super.onSaveInstanceState(); // 看到了没,这里有个版本检测,还有一段原因,所以各个版本的Android就有了不一致的行为
} // 所以在4.3(包括)以前ScrollView的scroll state是不会保存的。
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.scrollPosition = mScrollY; // 并且这里只save了mScrollY,可能你还需要更多的,比如mScrollX,
return ss; // 所以有这些原因在你一般都想要继承ScrollView然后实现自己的。
} @Override
protected void onRestoreInstanceState(Parcelable state) {
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Some old apps reused IDs in ways they shouldn't have.
// Don't break them, but they don't get scroll state restoration.
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState()); // 用super的state调用super的实现
mSavedState = ss;
requestLayout(); // 状态恢复了之后记得重新layout下,以便展现出来
}
好了言归正传,View的onSaveInstanceState和onRestoreInstanceState方法调用都是从Activity或Dialog的同名方法调用开始的,
这里我们看下Activity的对应实现,代码如下:
/**
* Called to retrieve per-instance state from an activity before being killed
* so that the state can be restored in {@link #onCreate} or
* {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
* will be passed to both).
*
* <p>This method is called before an activity may be killed so that when it
* comes back some time in the future it can restore its state. For example,
* if activity B is launched in front of activity A, and at some point activity
* A is killed to reclaim resources, activity A will have a chance to save the
* current state of its user interface via this method so that when the user
* returns to activity A, the state of the user interface can be restored
* via {@link #onCreate} or {@link #onRestoreInstanceState}.
*
* <p>Do not confuse this method with activity lifecycle callbacks such as
* {@link #onPause}, which is always called when an activity is being placed
* in the background or on its way to destruction, or {@link #onStop} which
* is called before destruction. One example of when {@link #onPause} and
* {@link #onStop} is called and not this method is when a user navigates back
* from activity B to activity A: there is no need to call {@link #onSaveInstanceState}
* on B because that particular instance will never be restored, so the
* system avoids calling it. An example when {@link #onPause} is called and
* not {@link #onSaveInstanceState} is when activity B is launched in front of activity A:
* the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't
* killed during the lifetime of B since the state of the user interface of
* A will stay intact.
*
* <p>The default implementation takes care of most of the UI per-instance
* state for you by calling {@link android.view.View#onSaveInstanceState()} on each
* view in the hierarchy that has an id, and by saving the id of the currently
* focused view (all of which is restored by the default implementation of
* {@link #onRestoreInstanceState}). If you override this method to save additional
* information not captured by each individual view, you will likely want to
* call through to the default implementation, otherwise be prepared to save
* all of the state of each view yourself.
*
* <p>If called, this method will occur before {@link #onStop}. There are
* no guarantees about whether it will occur before or after {@link #onPause}.
*
* @param outState Bundle in which to place your saved state.
*
* @see #onCreate
* @see #onRestoreInstanceState
* @see #onPause
*/
protected void onSaveInstanceState(Bundle outState) { // 此方法的doc非常长且详细,你需要认真阅读下
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); // 注意这里的mWindow.saveHierarchyState()调用
Parcelable p = mFragments.saveAllState(); // 从这里开始会调用到View层次结构中的对应方法
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
} /**
* This method is called after {@link #onStart} when the activity is
* being re-initialized from a previously saved state, given here in
* <var>savedInstanceState</var>. Most implementations will simply use {@link #onCreate}
* to restore their state, but it is sometimes convenient to do it here
* after all of the initialization has been done or to allow subclasses to
* decide whether to use your default implementation. The default
* implementation of this method performs a restore of any view state that
* had previously been frozen by {@link #onSaveInstanceState}.
*
* <p>This method is called between {@link #onStart} and
* {@link #onPostCreate}.
*
* @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
*
* @see #onCreate
* @see #onPostCreate
* @see #onResume
* @see #onSaveInstanceState
*/
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState); // 同样的调用Window的restoreHierarchyState方法
}
}
}
紧接着,我们看下Window中的实现:
public abstract Bundle saveHierarchyState();
public abstract void restoreHierarchyState(Bundle savedInstanceState);
// 我们看到Window中只是2个抽象方法,其具体实现还得看PhoneWindow类
/** {@inheritDoc} */
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle(); // new一个Bundle(其实现了Parcelable接口)
if (mContentParent == null) { // 这个字段还有印象吗?如果不清楚了你可以参看前面的这篇文章
return outState; // http://www.cnblogs.com/xiaoweiz/p/3787844.html
}
// 注意这里的container传递的是一个SparseArray,我们前面介绍过:http://www.cnblogs.com/xiaoweiz/p/3667689.html
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
mContentParent.saveHierarchyState(states); // 进入view层次结构的save state
outState.putSparseParcelableArray(VIEWS_TAG, states);
// save the focused view id
View focusedView = mContentParent.findFocus();
if (focusedView != null) {
if (focusedView.getId() != View.NO_ID) {
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
} else {
if (false) {
Log.d(TAG, "couldn't save which view has focus because the focused view "
+ focusedView + " has no id.");
}
}
}
// save the panels
SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
savePanelState(panelStates);
if (panelStates.size() > 0) {
outState.putSparseParcelableArray(PANELS_TAG, panelStates);
}
if (mActionBar != null) {
SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
mActionBar.saveHierarchyState(actionBarStates);
outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}
return outState;
}
/** {@inheritDoc} */
@Override
public void restoreHierarchyState(Bundle savedInstanceState) {
if (mContentParent == null) {
return;
}
SparseArray<Parcelable> savedStates
= savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if (savedStates != null) {
mContentParent.restoreHierarchyState(savedStates); // 同save的过程
}
// restore the focused view
int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
if (focusedViewId != View.NO_ID) {
View needsFocus = mContentParent.findViewById(focusedViewId);
if (needsFocus != null) {
needsFocus.requestFocus();
} else {
Log.w(TAG,
"Previously focused view reported id " + focusedViewId
+ " during save, but can't be found during restore.");
}
}
// restore the panels
SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
if (panelStates != null) {
restorePanelState(panelStates);
}
if (mActionBar != null) {
SparseArray<Parcelable> actionBarStates =
savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
if (actionBarStates != null) {
mActionBar.restoreHierarchyState(actionBarStates);
} else {
Log.w(TAG, "Missing saved instance states for action bar views! " +
"State will not be restored.");
}
}
}
这里由于ViewGroup没有覆写save/restoreHierarchyState()方法,所以最终调用的是View中的方法,这里我们看下其源码:
/**
* Store this view hierarchy's frozen state into the given container.
*
* @param container The SparseArray in which to save the view's state.
*
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #onSaveInstanceState()
*/
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container); // 调相应的dispatchXXX方法
} /**
* Called by {@link #saveHierarchyState(android.util.SparseArray)} to store the state for
* this view and its children. May be overridden to modify how freezing happens to a
* view's children; for example, some views may want to not store state for their children.
*
* @param container The SparseArray in which to save the view's state.
*
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
* @see #saveHierarchyState(android.util.SparseArray)
* @see #onSaveInstanceState()
*/
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {一个View必须有valid(非0)的mID,也就是说你
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { // 要么在xml里通过android:id指定要么在代码里通过setId
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; // 调用来设置,而且SAVE_DISABLED位没被打开,save才会发生
Parcelable state = onSaveInstanceState(); // 换句话说我们本文讲的所有东西都是和有valid id的View相关的,
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { // 和NO_ID的View无关
throw new IllegalStateException( // 注意这里的检测,也就是说子类必须要调用父类的onSaveInstanceState()方法,否则会抛异常
"Derived class did not call super.onSaveInstanceState()");
}
if (state != null) {
// Log.i("View", "Freezing #" + Integer.toHexString(mID)
// + ": " + state);
container.put(mID, state); // 这行代码,将state放进SparseArray中,以view自身的id为key,所以我们一开始的例子在这里
} // 就有问题了,key相同的情况下,后面的put会覆盖掉前面put的结果
}
} /**
* Hook allowing a view to generate a representation of its internal state
* that can later be used to create a new instance with that same state.
* This state should only contain information that is not persistent or can
* not be reconstructed later. For example, you will never store your
* current position on screen because that will be computed again when a
* new instance of the view is placed in its view hierarchy.
* <p>
* Some examples of things you may store here: the current cursor position
* in a text view (but usually not the text itself since that is stored in a
* content provider or other persistent storage), the currently selected
* item in a list view.
*
* @return Returns a Parcelable object containing the view's current dynamic
* state, or null if there is nothing interesting to save. The
* default implementation returns null.
* @see #onRestoreInstanceState(android.os.Parcelable)
* @see #saveHierarchyState(android.util.SparseArray)
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #setSaveEnabled(boolean)
*/
protected Parcelable onSaveInstanceState() { // callback方法或者也可以叫hook(钩子),允许客户代码覆写来实现自己的save逻辑
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; // 设置位标志,在dispatchXXX里当onSaveInstanceState返回时会再次检测这个位
return BaseSavedState.EMPTY_STATE; // 默认不save任何东西,也即do nothing
} /**
* Restore this view hierarchy's frozen state from the given container.
*
* @param container The SparseArray which holds previously frozen states.
*
* @see #saveHierarchyState(android.util.SparseArray)
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
public void restoreHierarchyState(SparseArray<Parcelable> container) {
dispatchRestoreInstanceState(container);
} /**
* Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
* state for this view and its children. May be overridden to modify how restoring
* happens to a view's children; for example, some views may want to not store state
* for their children.
*
* @param container The SparseArray which holds previously saved state.
*
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) {
Parcelable state = container.get(mID); // 通过id拿到saved state
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; // 关闭位标志,在onRestoreInstanceState里会再次打开它
onRestoreInstanceState(state);
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { // 检查有没有记得调用super的实现
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
}
}
} /**
* Hook allowing a view to re-apply a representation of its internal state that had previously
* been generated by {@link #onSaveInstanceState}. This function will never be called with a
* null state.
*
* @param state The frozen state that had previously been returned by
* {@link #onSaveInstanceState}.
*
* @see #onSaveInstanceState()
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
*/
protected void onRestoreInstanceState(Parcelable state) { // callback回调,在这里restore(save的反向过程)
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; // 打开位标志
if (state != BaseSavedState.EMPTY_STATE && state != null) { // 注意这个异常检测。。。
throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ "received " + state.getClass().toString() + " instead. This usually happens "
+ "when two views of different type have the same id in the same hierarchy. "
+ "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ "other views do not use the same id.");
}
}
最后,为了完整起见,我们看一个典型&简单的View子类对这2个方法的实现,android.widget.CompoundButton,源码如下:
@Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
setFreezesText(true);
Parcelable superState = super.onSaveInstanceState(); // 记得调用super的实现,否则会抛异常的 SavedState ss = new SavedState(superState); ss.checked = isChecked();
return ss; // 返回我们自己的状态
} @Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); // 同样记得调用super的实现
setChecked(ss.checked); // restore回来。。。
requestLayout(); // 重新layout下
}
这里再附上一个StackOverflow上关于此主题的问答帖:
现在为止,我们可以重新审视下Android中关于View id的说法了。官方的说法是在整个view树中id不一定非要唯一,但你至少要
保证在你搜索的这部分view树中是唯一的(局部唯一)。因为很显然,如果同一个layout文件中有2个id都是"android:id="@+id/button"
的Button,那你通过findViewById的时候只能找到前面的button,后面的那个就没机会被找到了,所以Android的说法是合理的。只是
在本文一开始那里的情况下,它没有提及,所以还应该加上特别重要的一条:当你的View确定要save/restore状态的时候,一定要保证
他们有unique的id!因为Android内部用id作为保存、恢复状态时使用的Key(SparseArray的key),否则就会发生一个覆盖另一个的
悲剧而你却得不到任何提示或警告。
这篇文章算是实际开发中的经验之谈,希望对大家的日常开发有所帮助,也希望能少一个走弯路、深夜debug的poor dev,enjoy。。。
View的onSaveInstanceState和onRestoreInstanceState过程分析的更多相关文章
- Android 中onSaveInstanceState和onRestoreInstanceState学习
1. 基本作用: Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate().onPaus ...
- onSaveInstanceState() 和 onRestoreInstanceState()
本文介绍Android中关于Activity的两个神秘方法:onSaveInstanceState() 和 onRestoreInstanceState(),并且在介绍这两个方法之后,再分别来实现使用 ...
- Android Activity的onSaveInstanceState() 和 onRestoreInstanceState()方法:
Android Activity的onSaveInstanceState() 和 onRestoreInstanceState()方法: 1. 基本作用: Activity的 onSaveInstan ...
- 容易被忽略的两个方法:onSaveInstanceState()和onRestoreInstanceState()
onSaveInstanceState()和onRestoreInstanceState()两个方法,在Activity中是比较容易忽视的方法,但是不得不说还是比较好用的方法,onSaveInstan ...
- Android onSaveInstanceState和onRestoreInstanceState()
首先来介绍onSaveInstanceState() 和 onRestoreInstanceState() .关于这两个方法,一些朋友可能在Android开发过程中很少用到,但在有时候掌握其用法会帮我 ...
- 转 onSaveInstanceState()和onRestoreInstanceState()使用详解
转 https://www.jianshu.com/p/27181e2e32d2 背景 如果系统由于系统约束(而不是正常的应用程序行为)而破坏了Activity,那么尽管实际 Activity实例已经 ...
- onSaveInstanceState和onRestoreInstanceState
本文摘自: http://h529820165.iteye.com/blog/1399023 Android calls onSaveInstanceState() before the activi ...
- 触发onSaveInstanceState和onRestoreInstanceState的时机
先看Application Fundamentals上的一段话: Android calls onSaveInstanceState() before the activity becomes ...
- 关于onsaveinstancestate和 onRestoreInstanceState()
之所以有这个话题,是因为工作遇到过两个问题.一个问题是页面空白,fragment重复创建.另一个问题是登录页用到了AutoCompleteTextView,调用showDropDown()方法导致cr ...
随机推荐
- html5的canvas绘制迷宫地图
canvas标签一直是html5的亮点,用它可以实现很多东西.我想用它来绘画像迷宫那样的地图.借助到的工具有瓦片地图编辑器tiled(点击跳转到下载链接). 如图:如果你想要画像这样的迷宫地图,如果不 ...
- QTableWidget控件总结<二>
QTableWidget是QT程序中常用的显示数据表格的空间,很类似于VC.C#中的DataGrid.说到QTableWidget,就必须讲一下它跟QTabelView的区别了.QTableWidge ...
- EntityFramework6.0的Sql读写分离拦截器 和 MVC的 Action拦截器 对比
EF的DbCommandInterceptor类 拦截: EF6.1也出来不少日子了,6.1相比6.0有个很大的特点就是新增了System.Data.Entity.Infrastructure.Int ...
- Lua使用心得(2)
在lua脚本调用中,如果我们碰到一种不好的脚本,例如: while 1 do do end 那我们的程序主线程也会被阻塞住.那我们如何防止这种问题呢?下面就给出一个解决的办法. 首先为了不阻塞主线程, ...
- 孙鑫MFC学习笔记5:文本显示
1.CreateSolidCaret添加一个插入符 参数:宽度,高度 如果设为0,就设为默认窗口边界的宽度和高度 2.GetSystemMetrics获取默认窗口边界的宽度和高度 3.Caret在创建 ...
- 从零开始学Python04作业源码:模拟ATM电子银行(仅供参考)
bin目录:程序启动入口 ATM_start.py: #!/usr/bin/python # -*- coding: utf-8 -*- # 模拟ATM电子银行+登录账户权限控制+管理员管理模块 # ...
- 解决 WinXP下 libcurl.dll 无法定位程序输入点GetTickCount64问题
1. 问题描述 用 IDA 打开libcurl.dll 可以在导入表看到对 GetTickCount64的引用,在 xp 的kernel32.dll中没有 GetTickCount64, 所以会出现 ...
- java顺序表和树的实现
一.顺序表 1.线性表 //java顺序表的实现,如ArrayList就是用线性表实现的,优点是查找快,缺点是添加或删除要移动很多元素,速度慢 public class SequenceList { ...
- easyui datagrid 分页略解
easyui datagrid 本身自带了分页功能. 但是这个需要你自己控制. 在后台可以得到两个datagrid的参数,rows 和page.其中rows是每页要显示的个数,page是第几页.单纯的 ...
- servlet同一用户不同页面共享数据
如何实现不同页面之间的数据传递,实现页面的数据共享?常见的方法有以下4种: 1)表单提交(form) 2)sendRedirect()跳转 3)session技术 4)Cookie技术 表单提交 这是 ...