Activity保存和恢复View层次结构

Activity异常生命周期中会涉及到onSaveInstanceState()   和    onRestoreInstanceState()

常见的异常情况有两种:   资源相关的系统配置发生改变          系统内存不足

资源相关的系统配置发生改变有:屏幕横竖屏切换,改变系统语言等

Activity异常情况下被杀死并重新创建的过程中就涉及到View的 保存和恢复,下面详细分析整个保存和恢复过程


1 举例子

假设我们这里有一个带有ImageView  ,TextView  和 Switch控件的简单布局

<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="horizontal"  
    android:padding="@dimen/activity_horizontal_margin">  
    <ImageView  
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"  
        android:src="@drawable/ic_launcher"/>  
    <TextView  
        android:layout_width="0dip"
        android:layout_weight="1"  
        android:layout_height="wrap_content"  
        android:text="My Text"/>  
    <Switch  
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"  
        android:layout_margin="8dip"/>  
</LinearLayout>
但是当我们滑动一下switch开关然后旋转屏幕方向,switch又回到了原来的状态。
通常,安卓会自动保存这些View(一般是系统控件)的状态,但是为什么在我们的案例中不起作用了呢?

从源码中我们可以得到答案




2 从宏观上看看保存和恢复的源码

  一、onSaveInstanceState

(1) Activity
首先从Activity中的onSaveInstanceState看起,毕竟,我们从这里开始
private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
protected void onSaveInstanceState(Bundle outState) {
    // 1、对Window里面的View树进行状态保存
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
    // 2、对Fragmet进行状态保存
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}
可以看到上面分为两个部分:
1、对View树进行状态保存

2、对Fragment进行状态保存

因为我们的Activity的视图是由View层次树或者Fragment构成。
这里调用了Window对象的saveHierarchyState,这里的mWindow对应于PhoneWindow,所以实际上调用了PhoneWindow.saveHierarchyState()


(2)PhoneWindow
@Override
    public Bundle saveHierarchyState() {
        Bundle outState = new Bundle();
        if (mContentParent == null) {
            return outState;
        }
	//数据保存的委托模式,从mContentParent(ViewGroup)开始,一层一层往下通知他的子元素
        SparseArray<Parcelable> states = new SparseArray<Parcelable>();
        mContentParent.saveHierarchyState(states);
        outState.putSparseParcelableArray(VIEWS_TAG, states);
	......
        return outState;
    }
我们主要来看看mContentParent.saveHierarchyState(states);mContentParent是整个Activity中View视图的顶层视图。它是一个ViewGroup类型。ViewGroup里面没有实现saveHierarchyState方法,它继承自View


(3) View

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

(4)ViewGroup
在ViewGroup中复写了dispatchSaveInstanceState
@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 c = children[i];
            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
                c.dispatchSaveInstanceState(container);
            }
        }
    }
首先调用了View的dispatchSaveInstanceState方法,目的是保存ViewGroup自身的当前状态
然后遍历ViewGroup的子View,调用每个子View的dispatchSaveInstanceState方法
再来看看View中的dispatchSaveInstanceState

(5)View
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
       ;
        //1、获取当前View需要保存的Parcelable
        Parcelable state = onSaveInstanceState();
        ......
        if (state != null) {
           ......
            container.put(mID, state);
        }
    }
}


从这里我们知道,如果一个view需要保存它的当前状态,就必须给这个view一个id,因为它将作为key来存放该view的状态。例子中没有给switch一个id,所以switch的状态没能保存下来。另外,如果我们需要保存一个View的当前状态,我们可以重写view它自己的onSaveInstanceState方法,把需要保存的内容进行保存进可以了


(6)Textview

这里我们可以看看Textview是怎么重写onSaveInstance()方法的

@Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        boolean save = mFreezesText;
        int start = 0;
        int end = 0;


        if (mText != null) {
            start = getSelectionStart();
            end = getSelectionEnd();
       ......
        }
	//在这里保存了选中位置的起点和终点
       if (save) 
       { SavedState ss = new SavedState(superState); 
       // XXX Should also save the current scroll position! ss.selStart = start; ss.selEnd = end;
 //在这里保存了文本的内容
 if (mText instanceof Spanned) 
       { Spannable sp = new SpannableStringBuilder(mText);
        ...... 
        ss.text = sp; } 
         else { ss.text = mText.toString(); } 
        ...... 
       if (mEditor != null) 
       { ss.editorState = mEditor.saveInstanceState(); } 
       return ss;
       } 
      return superState; }
从上面源码可以看出,Textview保存了自己的文本选中状态和文本内容


二、onRestoreInstanceState

(1) Activity
还是从Activity开始,先看看Activity的onRestoreInstanceState
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    if (mWindow != null) {
        // 1、得到保存的状态
        Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
        if (windowState != null) {
            // 2、状态恢复
            mWindow.restoreHierarchyState(windowState);
        }
    }
}

(2)PhoneWindow
轻车熟路,这里mWindow是指PhoneWindow上面已经说过了。
@Override
public void restoreHierarchyState(Bundle savedInstanceState) {
    if (mContentParent == null) {
        return;
    }
    // 得到所有View保存的状态
    SparseArray<Parcelable> savedStates
            = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
    if (savedStates != null) {
        mContentParent.restoreHierarchyState(savedStates);
    }
    ......
}

mContentParent是ViewGroup,ViewGroup没有重写restoreHierarchyState方法,它继承子View,下面来看看View的restoreHierarchyState代码,

(3)View
public void restoreHierarchyState(SparseArray<Parcelable> container) {
        dispatchRestoreInstanceState(container);
    }

(4)ViewGroup
ViewGroup复写了dispatchRestoreInstanceState
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    // 1、执行父类的dispatchRestoreInstanceState方法,也就是View的dispatchRestoreInstanceState方法
    // 目的是恢复当前ViewGroup的状态


    // 2、递归遍历所有的子View来恢复所有子View的状态
    super.dispatchRestoreInstanceState(container);
    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        View c = children[i];
        if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
            c.dispatchRestoreInstanceState(container);
        }
    }
}

(5)View
再来看看view中的dispatchRestoreInstanceState
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    if (mID != NO_ID) {
        // 1、得到这个View保存的状态
        Parcelable state = container.get(mID);
        if (state != null) {
           ......
            onRestoreInstanceState(state);
            ......
        }
    }
}

所以如果我们的一个View重写了onSaveInstanceState()方法进行了状态的保存,相应的就应该重写onRestoreInstanceState方法进行相应的状态恢复。


(6)Textview

我们看看Textview怎么恢复数据的

@Override
    public void onRestoreInstanceState(Parcelable state) {
        ......
        SavedState ss = (SavedState)state;
        super.onRestoreInstanceState(ss.getSuperState());


        // 恢复文本的显示
        if (ss.text != null) {
            setText(ss.text);
        }


        if (ss.selStart >= 0 && ss.selEnd >= 0) {
            if (mText instanceof Spannable) {
               ......
                    Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);


               ......
                }
            }
        }
               ......
        if (ss.editorState != null) {
            createEditorIfNeeded();
            mEditor.restoreInstanceState(ss.editorState);
        }
    }
从onRestoreInstanceState可以看出,的确对文本选中状态和文本内容进行了恢复


3 总结

1 要保存View的状态,View必须要有id,   SparseArray<Parcelable> container用来保存view的状态,这个容器的key就是每个view的id,value就是每个view的状态


整个saveInstanceState过程和restoreInstanceState过程都是一个委派的过程,从Activity到PhoneWindow,此过程创建sparseArray集合(稀疏数组),用来保存每个控件的状态对象(序列化对象Parcelable),然后把spareArray保存在Bundle中,接着调用viewGroup的dispatchSaveInstanceState方法,该方法会遍历viewGroup中的每个子元素,将子元素中onSaveInstanceState返回的结果保存作为sparseArray的值,控件的id作为sparseArray的键。


onSaveInstanceState则是从Bundle中获取到相应的sparseArray集合,接着调用ViewGroup的dispatchRestoreInstanceState方法,该方法会遍历viewGroup的每个子元素,然后从sparseArray中获取相应的状态对象(通过控件的id)并传入onRestoreInstanceState中


Activity中saveInstanceState(Bundle)和restoreInstanceState(Bundle)的bundle从何而来?运行机制?为什么Activity销毁后还可以从Bundle中恢复?

Bundle是序列化对象,Activity的Bundle保存了ViewGroup中创建的Bundle,ViewGroup的Bundle保存了sparseArray,sparseArray保存了控件的状态。

那么Activity中的Bundle来自哪里?怎么管理的?

猜测:Activity在容易销毁时会创建一个Bundle对象,然后传入saveInstanceState(Bundle)中,当Activity销毁时,序列化对象Bundle与相应的Activity会建立相应映射关系,并保存在AMS的集合中,当Activity重建时会去看是否有相应的Bundle存在,存在则恢复状态,此过程涉及到binder通信。


2  整个保存view层次的流程图如下:

(1)Activity方法onSaveInstanceState中    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

(2)PhoneWindow方法saveHierarchyState中,

         Bundle outState = new Bundle();

         SparseArray<Parcelable> states = new SparseArray<Parcelable>();

         mContentParent.saveHierarchyState(states);
         outState.putSparseParcelableArray(VIEWS_TAG, states);

  (3)ViewGroup中

         for (int i = 0; i < count; i++) {
         c.dispatchSaveInstanceState(container);
         }

  (4)View

         Parcelable state = onSaveInstanceState();

         container.put(mID, state);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值