为什么要介绍这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之间没做好,看眼源码:

  1. @Override
  2. protected Parcelable onSaveInstanceState() {
  3. if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
  4. // Some old apps reused IDs in ways they shouldn't have.
  5. // Don't break them, but they don't get scroll state restoration.
  6. return super.onSaveInstanceState(); // 看到了没,这里有个版本检测,还有一段原因,所以各个版本的Android就有了不一致的行为
  7. } // 所以在4.3(包括)以前ScrollView的scroll state是不会保存的。
  8. Parcelable superState = super.onSaveInstanceState();
  9. SavedState ss = new SavedState(superState);
  10. ss.scrollPosition = mScrollY; // 并且这里只save了mScrollY,可能你还需要更多的,比如mScrollX,
  11. return ss; // 所以有这些原因在你一般都想要继承ScrollView然后实现自己的。
  12. }
  13.  
  14. @Override
  15. protected void onRestoreInstanceState(Parcelable state) {
  16. if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
  17. // Some old apps reused IDs in ways they shouldn't have.
  18. // Don't break them, but they don't get scroll state restoration.
  19. super.onRestoreInstanceState(state);
  20. return;
  21. }
  22. SavedState ss = (SavedState) state;
  23. super.onRestoreInstanceState(ss.getSuperState()); // 用super的state调用super的实现
  24. mSavedState = ss;
  25. requestLayout(); // 状态恢复了之后记得重新layout下,以便展现出来
  26. }

  好了言归正传,View的onSaveInstanceState和onRestoreInstanceState方法调用都是从Activity或Dialog的同名方法调用开始的,

这里我们看下Activity的对应实现,代码如下:

  1. /**
  2. * Called to retrieve per-instance state from an activity before being killed
  3. * so that the state can be restored in {@link #onCreate} or
  4. * {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
  5. * will be passed to both).
  6. *
  7. * <p>This method is called before an activity may be killed so that when it
  8. * comes back some time in the future it can restore its state. For example,
  9. * if activity B is launched in front of activity A, and at some point activity
  10. * A is killed to reclaim resources, activity A will have a chance to save the
  11. * current state of its user interface via this method so that when the user
  12. * returns to activity A, the state of the user interface can be restored
  13. * via {@link #onCreate} or {@link #onRestoreInstanceState}.
  14. *
  15. * <p>Do not confuse this method with activity lifecycle callbacks such as
  16. * {@link #onPause}, which is always called when an activity is being placed
  17. * in the background or on its way to destruction, or {@link #onStop} which
  18. * is called before destruction. One example of when {@link #onPause} and
  19. * {@link #onStop} is called and not this method is when a user navigates back
  20. * from activity B to activity A: there is no need to call {@link #onSaveInstanceState}
  21. * on B because that particular instance will never be restored, so the
  22. * system avoids calling it. An example when {@link #onPause} is called and
  23. * not {@link #onSaveInstanceState} is when activity B is launched in front of activity A:
  24. * the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't
  25. * killed during the lifetime of B since the state of the user interface of
  26. * A will stay intact.
  27. *
  28. * <p>The default implementation takes care of most of the UI per-instance
  29. * state for you by calling {@link android.view.View#onSaveInstanceState()} on each
  30. * view in the hierarchy that has an id, and by saving the id of the currently
  31. * focused view (all of which is restored by the default implementation of
  32. * {@link #onRestoreInstanceState}). If you override this method to save additional
  33. * information not captured by each individual view, you will likely want to
  34. * call through to the default implementation, otherwise be prepared to save
  35. * all of the state of each view yourself.
  36. *
  37. * <p>If called, this method will occur before {@link #onStop}. There are
  38. * no guarantees about whether it will occur before or after {@link #onPause}.
  39. *
  40. * @param outState Bundle in which to place your saved state.
  41. *
  42. * @see #onCreate
  43. * @see #onRestoreInstanceState
  44. * @see #onPause
  45. */
  46. protected void onSaveInstanceState(Bundle outState) { // 此方法的doc非常长且详细,你需要认真阅读下
  47. outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); // 注意这里的mWindow.saveHierarchyState()调用
  48. Parcelable p = mFragments.saveAllState(); // 从这里开始会调用到View层次结构中的对应方法
  49. if (p != null) {
  50. outState.putParcelable(FRAGMENTS_TAG, p);
  51. }
  52. getApplication().dispatchActivitySaveInstanceState(this, outState);
  53. }
  54.  
  55. /**
  56. * This method is called after {@link #onStart} when the activity is
  57. * being re-initialized from a previously saved state, given here in
  58. * <var>savedInstanceState</var>. Most implementations will simply use {@link #onCreate}
  59. * to restore their state, but it is sometimes convenient to do it here
  60. * after all of the initialization has been done or to allow subclasses to
  61. * decide whether to use your default implementation. The default
  62. * implementation of this method performs a restore of any view state that
  63. * had previously been frozen by {@link #onSaveInstanceState}.
  64. *
  65. * <p>This method is called between {@link #onStart} and
  66. * {@link #onPostCreate}.
  67. *
  68. * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
  69. *
  70. * @see #onCreate
  71. * @see #onPostCreate
  72. * @see #onResume
  73. * @see #onSaveInstanceState
  74. */
  75. protected void onRestoreInstanceState(Bundle savedInstanceState) {
  76. if (mWindow != null) {
  77. Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
  78. if (windowState != null) {
  79. mWindow.restoreHierarchyState(windowState); // 同样的调用Window的restoreHierarchyState方法
  80. }
  81. }
  82. }

  紧接着,我们看下Window中的实现:

  1. public abstract Bundle saveHierarchyState();
  2.  
  3. public abstract void restoreHierarchyState(Bundle savedInstanceState);
  4.  
  5. // 我们看到Window中只是2个抽象方法,其具体实现还得看PhoneWindow类
  6.  
  7. /** {@inheritDoc} */
  8. @Override
  9. public Bundle saveHierarchyState() {
  10. Bundle outState = new Bundle(); // new一个Bundle(其实现了Parcelable接口)
  11. if (mContentParent == null) { // 这个字段还有印象吗?如果不清楚了你可以参看前面的这篇文章
  12. return outState; // http://www.cnblogs.com/xiaoweiz/p/3787844.html
  13. }
  14. // 注意这里的container传递的是一个SparseArray,我们前面介绍过:http://www.cnblogs.com/xiaoweiz/p/3667689.html
  15. SparseArray<Parcelable> states = new SparseArray<Parcelable>();
  16. mContentParent.saveHierarchyState(states); // 进入view层次结构的save state
  17. outState.putSparseParcelableArray(VIEWS_TAG, states);
  18.  
  19. // save the focused view id
  20. View focusedView = mContentParent.findFocus();
  21. if (focusedView != null) {
  22. if (focusedView.getId() != View.NO_ID) {
  23. outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
  24. } else {
  25. if (false) {
  26. Log.d(TAG, "couldn't save which view has focus because the focused view "
  27. + focusedView + " has no id.");
  28. }
  29. }
  30. }
  31.  
  32. // save the panels
  33. SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
  34. savePanelState(panelStates);
  35. if (panelStates.size() > 0) {
  36. outState.putSparseParcelableArray(PANELS_TAG, panelStates);
  37. }
  38.  
  39. if (mActionBar != null) {
  40. SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
  41. mActionBar.saveHierarchyState(actionBarStates);
  42. outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
  43. }
  44.  
  45. return outState;
  46. }
  47.  
  48. /** {@inheritDoc} */
  49. @Override
  50. public void restoreHierarchyState(Bundle savedInstanceState) {
  51. if (mContentParent == null) {
  52. return;
  53. }
  54.  
  55. SparseArray<Parcelable> savedStates
  56. = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
  57. if (savedStates != null) {
  58. mContentParent.restoreHierarchyState(savedStates); // 同save的过程
  59. }
  60.  
  61. // restore the focused view
  62. int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
  63. if (focusedViewId != View.NO_ID) {
  64. View needsFocus = mContentParent.findViewById(focusedViewId);
  65. if (needsFocus != null) {
  66. needsFocus.requestFocus();
  67. } else {
  68. Log.w(TAG,
  69. "Previously focused view reported id " + focusedViewId
  70. + " during save, but can't be found during restore.");
  71. }
  72. }
  73.  
  74. // restore the panels
  75. SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
  76. if (panelStates != null) {
  77. restorePanelState(panelStates);
  78. }
  79.  
  80. if (mActionBar != null) {
  81. SparseArray<Parcelable> actionBarStates =
  82. savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
  83. if (actionBarStates != null) {
  84. mActionBar.restoreHierarchyState(actionBarStates);
  85. } else {
  86. Log.w(TAG, "Missing saved instance states for action bar views! " +
  87. "State will not be restored.");
  88. }
  89. }
  90. }

这里由于ViewGroup没有覆写save/restoreHierarchyState()方法,所以最终调用的是View中的方法,这里我们看下其源码:

  1. /**
  2. * Store this view hierarchy's frozen state into the given container.
  3. *
  4. * @param container The SparseArray in which to save the view's state.
  5. *
  6. * @see #restoreHierarchyState(android.util.SparseArray)
  7. * @see #dispatchSaveInstanceState(android.util.SparseArray)
  8. * @see #onSaveInstanceState()
  9. */
  10. public void saveHierarchyState(SparseArray<Parcelable> container) {
  11. dispatchSaveInstanceState(container); // 调相应的dispatchXXX方法
  12. }
  13.  
  14. /**
  15. * Called by {@link #saveHierarchyState(android.util.SparseArray)} to store the state for
  16. * this view and its children. May be overridden to modify how freezing happens to a
  17. * view's children; for example, some views may want to not store state for their children.
  18. *
  19. * @param container The SparseArray in which to save the view's state.
  20. *
  21. * @see #dispatchRestoreInstanceState(android.util.SparseArray)
  22. * @see #saveHierarchyState(android.util.SparseArray)
  23. * @see #onSaveInstanceState()
  24. */
  25. protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {一个View必须有valid(非0)的mID,也就是说你
  26. if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { // 要么在xml里通过android:id指定要么在代码里通过setId
  27. mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; // 调用来设置,而且SAVE_DISABLED位没被打开,save才会发生
  28. Parcelable state = onSaveInstanceState(); // 换句话说我们本文讲的所有东西都是和有valid id的View相关的,
  29. if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { // 和NO_ID的View无关
  30. throw new IllegalStateException( // 注意这里的检测,也就是说子类必须要调用父类的onSaveInstanceState()方法,否则会抛异常
  31. "Derived class did not call super.onSaveInstanceState()");
  32. }
  33. if (state != null) {
  34. // Log.i("View", "Freezing #" + Integer.toHexString(mID)
  35. // + ": " + state);
  36. container.put(mID, state); // 这行代码,将state放进SparseArray中,以view自身的id为key,所以我们一开始的例子在这里
  37. } // 就有问题了,key相同的情况下,后面的put会覆盖掉前面put的结果
  38. }
  39. }
  40.  
  41. /**
  42. * Hook allowing a view to generate a representation of its internal state
  43. * that can later be used to create a new instance with that same state.
  44. * This state should only contain information that is not persistent or can
  45. * not be reconstructed later. For example, you will never store your
  46. * current position on screen because that will be computed again when a
  47. * new instance of the view is placed in its view hierarchy.
  48. * <p>
  49. * Some examples of things you may store here: the current cursor position
  50. * in a text view (but usually not the text itself since that is stored in a
  51. * content provider or other persistent storage), the currently selected
  52. * item in a list view.
  53. *
  54. * @return Returns a Parcelable object containing the view's current dynamic
  55. * state, or null if there is nothing interesting to save. The
  56. * default implementation returns null.
  57. * @see #onRestoreInstanceState(android.os.Parcelable)
  58. * @see #saveHierarchyState(android.util.SparseArray)
  59. * @see #dispatchSaveInstanceState(android.util.SparseArray)
  60. * @see #setSaveEnabled(boolean)
  61. */
  62. protected Parcelable onSaveInstanceState() { // callback方法或者也可以叫hook(钩子),允许客户代码覆写来实现自己的save逻辑
  63. mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; // 设置位标志,在dispatchXXX里当onSaveInstanceState返回时会再次检测这个位
  64. return BaseSavedState.EMPTY_STATE; // 默认不save任何东西,也即do nothing
  65. }
  66.  
  67. /**
  68. * Restore this view hierarchy's frozen state from the given container.
  69. *
  70. * @param container The SparseArray which holds previously frozen states.
  71. *
  72. * @see #saveHierarchyState(android.util.SparseArray)
  73. * @see #dispatchRestoreInstanceState(android.util.SparseArray)
  74. * @see #onRestoreInstanceState(android.os.Parcelable)
  75. */
  76. public void restoreHierarchyState(SparseArray<Parcelable> container) {
  77. dispatchRestoreInstanceState(container);
  78. }
  79.  
  80. /**
  81. * Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
  82. * state for this view and its children. May be overridden to modify how restoring
  83. * happens to a view's children; for example, some views may want to not store state
  84. * for their children.
  85. *
  86. * @param container The SparseArray which holds previously saved state.
  87. *
  88. * @see #dispatchSaveInstanceState(android.util.SparseArray)
  89. * @see #restoreHierarchyState(android.util.SparseArray)
  90. * @see #onRestoreInstanceState(android.os.Parcelable)
  91. */
  92. protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
  93. if (mID != NO_ID) {
  94. Parcelable state = container.get(mID); // 通过id拿到saved state
  95. if (state != null) {
  96. // Log.i("View", "Restoreing #" + Integer.toHexString(mID)
  97. // + ": " + state);
  98. mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; // 关闭位标志,在onRestoreInstanceState里会再次打开它
  99. onRestoreInstanceState(state);
  100. if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { // 检查有没有记得调用super的实现
  101. throw new IllegalStateException(
  102. "Derived class did not call super.onRestoreInstanceState()");
  103. }
  104. }
  105. }
  106. }
  107.  
  108. /**
  109. * Hook allowing a view to re-apply a representation of its internal state that had previously
  110. * been generated by {@link #onSaveInstanceState}. This function will never be called with a
  111. * null state.
  112. *
  113. * @param state The frozen state that had previously been returned by
  114. * {@link #onSaveInstanceState}.
  115. *
  116. * @see #onSaveInstanceState()
  117. * @see #restoreHierarchyState(android.util.SparseArray)
  118. * @see #dispatchRestoreInstanceState(android.util.SparseArray)
  119. */
  120. protected void onRestoreInstanceState(Parcelable state) { // callback回调,在这里restore(save的反向过程)
  121. mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; // 打开位标志
  122. if (state != BaseSavedState.EMPTY_STATE && state != null) { // 注意这个异常检测。。。
  123. throw new IllegalArgumentException("Wrong state class, expecting View State but "
  124. + "received " + state.getClass().toString() + " instead. This usually happens "
  125. + "when two views of different type have the same id in the same hierarchy. "
  126. + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
  127. + "other views do not use the same id.");
  128. }
  129. }

  最后,为了完整起见,我们看一个典型&简单的View子类对这2个方法的实现,android.widget.CompoundButton,源码如下:

  1. @Override
  2. public Parcelable onSaveInstanceState() {
  3. // Force our ancestor class to save its state
  4. setFreezesText(true);
  5. Parcelable superState = super.onSaveInstanceState(); // 记得调用super的实现,否则会抛异常的
  6.  
  7. SavedState ss = new SavedState(superState);
  8.  
  9. ss.checked = isChecked();
  10. return ss; // 返回我们自己的状态
  11. }
  12.  
  13. @Override
  14. public void onRestoreInstanceState(Parcelable state) {
  15. SavedState ss = (SavedState) state;
  16.  
  17. super.onRestoreInstanceState(ss.getSuperState()); // 同样记得调用super的实现
  18. setChecked(ss.checked); // restore回来。。。
  19. requestLayout(); // 重新layout下
  20. }

这里再附上一个StackOverflow上关于此主题的问答帖:

http://stackoverflow.com/questions/3542333/how-to-prevent-custom-views-from-losing-state-across-screen-orientation-changes

  现在为止,我们可以重新审视下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过程分析的更多相关文章

  1. Android 中onSaveInstanceState和onRestoreInstanceState学习

    1. 基本作用: Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate().onPaus ...

  2. onSaveInstanceState() 和 onRestoreInstanceState()

    本文介绍Android中关于Activity的两个神秘方法:onSaveInstanceState() 和 onRestoreInstanceState(),并且在介绍这两个方法之后,再分别来实现使用 ...

  3. Android Activity的onSaveInstanceState() 和 onRestoreInstanceState()方法:

    Android Activity的onSaveInstanceState() 和 onRestoreInstanceState()方法: 1. 基本作用: Activity的 onSaveInstan ...

  4. 容易被忽略的两个方法:onSaveInstanceState()和onRestoreInstanceState()

    onSaveInstanceState()和onRestoreInstanceState()两个方法,在Activity中是比较容易忽视的方法,但是不得不说还是比较好用的方法,onSaveInstan ...

  5. Android onSaveInstanceState和onRestoreInstanceState()

    首先来介绍onSaveInstanceState() 和 onRestoreInstanceState() .关于这两个方法,一些朋友可能在Android开发过程中很少用到,但在有时候掌握其用法会帮我 ...

  6. 转 onSaveInstanceState()和onRestoreInstanceState()使用详解

    转 https://www.jianshu.com/p/27181e2e32d2 背景 如果系统由于系统约束(而不是正常的应用程序行为)而破坏了Activity,那么尽管实际 Activity实例已经 ...

  7. onSaveInstanceState和onRestoreInstanceState

    本文摘自: http://h529820165.iteye.com/blog/1399023 Android calls onSaveInstanceState() before the activi ...

  8. 触发onSaveInstanceState和onRestoreInstanceState的时机

    先看Application Fundamentals上的一段话:    Android calls onSaveInstanceState() before the activity becomes ...

  9. 关于onsaveinstancestate和 onRestoreInstanceState()

    之所以有这个话题,是因为工作遇到过两个问题.一个问题是页面空白,fragment重复创建.另一个问题是登录页用到了AutoCompleteTextView,调用showDropDown()方法导致cr ...

随机推荐

  1. rtf格式的一些说明,转载的

    RTF是Rich TextFormat的缩写,意即多文本格式.这是一种类似DOC格式(Word文档)的文件,有很好的兼容性,使用Windows"附件"中的"写字板&quo ...

  2. MySQL 5.1 参考手册CHM (官方 简体中文版)

    点此下载: MySQL 5.1 参考手册CHM (官方 简体中文版) 在线文档:http://doc.mysql.cn/mysql5/refman-5.1-zh.html-chapter/

  3. sqlserver 中存储过程的基础知识记录

    1.什么是存储过程? 存储过程就是作为可执行对象存放在数据库中的一个或多个SQL命令. 通俗来讲:存储过程其实就是能完成一定操作的一组SQL语句. 2.为什么要用存储过程? 1)存储过程只在创建时进行 ...

  4. SSH框架执行自己定义的SQL语句

    直接上代码 String hsql = "delete XTable x where x.Userid= ?"; Query query = this.getSession().c ...

  5. MyEclipse 不能将WAR包导出的解决方法

    不能导出WAR包的原因是破解没有完全导致的. 解决办法: 找到MyEclipse安装目录下MyEclipse\Common\plugins文件夹中的com.genuitec.eclipse.expor ...

  6. IOS 集成第三方登录

    我使用的是友盟上集成的第三方登录功能,一共使用了三个应用的登录授权,QQ.微信.新浪微博.由于第三方登录授权成功后,需要跳转到一个新的界面,所以这里需要在项目里设置第三方登录的SSO授权.就是必须安装 ...

  7. ThinkCMF-smeta扩展字段

    ThinkCMF - 添加文章功能 没有上传文件功能,为了扩展这一功能,在页面加入如下代码: <tr> <td> <div style="text-align: ...

  8. [moka同学笔记]Yii2中多表关联查询(join、joinwith) (摘录)

    表结构 现在有客户表.订单表.图书表.作者表, 客户表Customer   (id  customer_name) 订单表Order          (id  order_name       cu ...

  9. 【转】编译Lua5.3.0的iOS静态库

    This is a tutorial on how to compile Lua 5.3.0 as an iOS static library (liblua.a) on Mac OS X 10.10 ...

  10. opencart 后台导航菜单添加步骤

    1,找到在catalog\language\english\common\header.php // Text$_['text_affiliate'] = 'Affiliates';$_['text_ ...