View 和 AbsListView中的onSaveInstanceState()方法以及onRestoreInstanceState()方法的使用

本文详细解析了Android中View、AbsListView及Activity等组件的状态保存方法onSaveInstanceState和onRestoreInstanceState。介绍了它们之间的区别和联系,并通过实例展示了如何在ListView中应用这些方法来保持用户界面的一致性。

看项目代码时发现View和AbsListView中同样有onSaveInstanceState和onRestoreInstanceState方法


这两个类中的onSaveInstanceState()方法与Activity类的有些不同,当然原理和用途都是一致,都是为了保存控件当前的一些状态。

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) {
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}
view中:

/**
 * 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() {
    mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
    return BaseSavedState.EMPTY_STATE;
}

AbsListView中:

@Override
public Parcelable onSaveInstanceState() {
    /*
     * This doesn't really make sense as the place to dismiss the
     * popups, but there don't seem to be any other useful hooks
     * that happen early enough to keep from getting complaints
     * about having leaked the window.
     */
    dismissPopup();

    Parcelable superState = super.onSaveInstanceState();

    SavedState ss = new SavedState(superState);

    if (mPendingSync != null) {
        // Just keep what we last restored.
        ss.selectedId = mPendingSync.selectedId;
        ss.firstId = mPendingSync.firstId;
        ss.viewTop = mPendingSync.viewTop;
        ss.position = mPendingSync.position;
        ss.height = mPendingSync.height;
        ss.filter = mPendingSync.filter;
        ss.inActionMode = mPendingSync.inActionMode;
        ss.checkedItemCount = mPendingSync.checkedItemCount;
        ss.checkState = mPendingSync.checkState;
        ss.checkIdState = mPendingSync.checkIdState;
        return ss;
    }

    boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
    long selectedId = getSelectedItemId();
    ss.selectedId = selectedId;
    ss.height = getHeight();

    if (selectedId >= 0) {
        // Remember the selection
        ss.viewTop = mSelectedTop;
        ss.position = getSelectedItemPosition();
        ss.firstId = INVALID_POSITION;
    } else {
        if (haveChildren && mFirstPosition > 0) {
            // Remember the position of the first child.
            // We only do this if we are not currently at the top of
            // the list, for two reasons:
            // (1) The list may be in the process of becoming empty, in
            // which case mItemCount may not be 0, but if we try to
            // ask for any information about position 0 we will crash.
            // (2) Being "at the top" seems like a special case, anyway,
            // and the user wouldn't expect to end up somewhere else when
            // they revisit the list even if its content has changed.
            View v = getChildAt(0);
            ss.viewTop = v.getTop();
            int firstPos = mFirstPosition;
            if (firstPos >= mItemCount) {
                firstPos = mItemCount - 1;
            }
            ss.position = firstPos;
            ss.firstId = mAdapter.getItemId(firstPos);
        } else {
            ss.viewTop = 0;
            ss.firstId = INVALID_POSITION;
            ss.position = 0;
        }
    }

    ss.filter = null;
    if (mFiltered) {
        final EditText textFilter = mTextFilter;
        if (textFilter != null) {
            Editable filterText = textFilter.getText();
            if (filterText != null) {
                ss.filter = filterText.toString();
            }
        }
    }

    ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;

    if (mCheckStates != null) {
        ss.checkState = mCheckStates.clone();
    }
    if (mCheckedIdStates != null) {
        final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
        final int count = mCheckedIdStates.size();
        for (int i = 0; i < count; i++) {
            idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
        }
        ss.checkIdState = idState;
    }
    ss.checkedItemCount = mCheckedItemCount;

    if (mRemoteAdapter != null) {
        mRemoteAdapter.saveRemoteViewsCache();
    }

    return ss;
}
从源码大致可以看出他们各自保存的数据并不同,这跟各个控件的用途特性有关。比如listview会对当前选中的item项位置进行保存,而View的其他一些子类比如Edittext由于存在光标,会对光标位置信息等进行保存。View和Abslistview的onsaveinstancestate方法返回的是Parcelable对象,Activity无返回。但不管返回的是什么,存储信息的都是Parcelable或其子类。

使用举例:

@Override
protected void onPause() {
   super.onPause();
   try {
      lv_friend_state = lv_friend.onSaveInstanceState();
   } catch (RuntimeException e) {
   }
}
界面onpause时对listview状态进行保存

之后在onresume中恢复

@Override
protected void onResume() {
   super.onResume();
   
      lv_friend.onRestoreInstanceState(lv_friend_state);
   
}
以前开发遇到过类似要求页面跳转后回来重新请求网络数据后保持历史操作的一些界面不变,当时是通过记录listview等的item的position数据重新进行定位实现的。现在通过状态保存可以很方便的实现像Activity状态保存的类似需求,能够更方便的实现一些效果。

当然还有其他的一些类也有这些方法,没有仔细全找出来。相对应的onRestoreInstanceState里面的代码逻辑也相应各有不同,与onsaveinstancestate相呼应。

07-21 16:57:24.053 18140 18140 I ls_test : removeUsbDevice 07-21 16:57:24.053 18140 18140 I MicHelper: mode:3 07-21 16:57:24.054 4919 4919 I HwCarCommonSDK:VehicleConfigurationWord: Get cfg val by dynamic_projection_lamp 07-21 16:57:24.054 4919 4919 E HwCarCommonSDK:VehicleConfigurationWord: Don't support : dynamic_projection_lamp, please check configuration xml. 07-21 16:57:24.054 4919 4919 I HwCarCommonSDK:VehicleConfigurationWord: Get cfg val by dynamic_lamp_projection_type 07-21 16:57:24.054 4919 4919 E HwCarCommonSDK:VehicleConfigurationWord: Don't support : dynamic_lamp_projection_type, please check configuration xml. 07-21 16:57:24.054 4919 4919 E HwCarCommonSDK:BaseCfg: wordValue is invalid, usually caused by empty word info map. 07-21 16:57:24.054 614 614 I android.system.suspend@1.0-service: decSuspendCounter kname=native_ApmAudio --mSuspendCounter= 4 07-21 16:57:24.054 614 614 I android.system.suspend@1.0-service: incSuspendCounter kname=native_ApmOutput ++mSuspendCounter= 5 07-21 16:57:24.054 1211 21300 W APM::AudioInputDescriptor: close client with port ID 510 still active on input 509 07-21 16:57:24.054 1211 21300 I APM::AudioInputDescriptor: setClientActive(uid:1000, session:369, state:2, silenced:0 active:0) 07-21 16:57:24.054 1211 21300 I APM::AudioInputDescriptor: stop, input handle 686, profile name: mixport_input_bus_voice_upload, curActiveCount: 0 07-21 16:57:24.054 1211 21300 I AudioFlinger: closeInput() 686 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: Config is relaunching invisible activity ActivityRecord{cb10d7a u10 com.huawei.hmsauto.cast.car/.projection.view.ProjectionActivity t1000112}, changes=0x30 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: relaunch this:ActivityRecord{cb10d7a u10 com.huawei.hmsauto.cast.car/.projection.view.ProjectionActivity t1000112}, andResume:true 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: java.lang.Exception 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.ActivityRecord.relaunchActivityLocked(ActivityRecord.java:11708) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:11456) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:11215) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.ActivityTaskManagerService.ensureConfigAndVisibilityAfterUpdate(ActivityTaskManagerService.java:6146) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.DisplayContent.updateDisplayOverrideConfigurationLocked(DisplayContent.java:7543) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.DisplayContent.updateDisplayOverrideConfigurationLocked(DisplayContent.java:7504) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.DisplayContent.sendNewConfiguration(DisplayContent.java:1903) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.InputManagerCallback$$ExternalSyntheticLambda2.accept(Unknown Source:2) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.RootWindowContainer.forAllDisplays(RootWindowContainer.java:1671) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.wm.InputManagerCallback.notifyConfigurationChanged(InputManagerCallback.java:142) 07-21 16:57:24.055 2528 2773 I ActivityTaskManager: at com.android.server.input.InputManagerService.notifyConfigurationChanged(InputManagerService.java:3487) 07-21 16:57:24.055 614 614 I android.system.suspend@1.0-service: decSuspendCounter kname=native_ApmOutput --mSuspendCounter= 4 07-21 16:57:24.055 2528 2773 E ActivityTaskManager: ensureVisibilityAndConfig starting:ActivityRecord{cb10d7a u10 com.huawei.hmsauto.cast.car/.projection.view.ProjectionActivity t1000112}, kept:false 07-21 16:57:24.055 25188 25202 I LifecycleTransaction: activityCallbacks ActivityRelaunchItem{pendingResults=null,pendingNewIntents=null,configChanges=48,config={mGlobalConfig={1.0 ?mcc?mnc [zh_CN_#Hans] ldltr sw540dp w960dp h444dp 320dpi nrml long hdr widecg land car finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1920, 1080) mAppBounds=Rect(0, 0 - 1920, 984) mMaxBounds=Rect(0, 0 - 1920, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0 mPopOverMode=0} hwt:1 suim:1 extflag:8 s.3972 fontWeightAdjustment=0} mOverrideConfig={1.0 ?mcc?mnc [zh_CN_#Hans] ldltr sw540dp w960dp h444dp 320dpi nrml long hdr widecg land car finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1920, 1080) mAppBounds=Rect(0, 0 - 1920, 984) mMaxBounds=Rect(0, 0 - 1920, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0 mPopOverMode=0} hwt:1 suim:1 userId:-10000 extflag:8 s.2 fontWeightAdjustment=0}},preserveWindowfalse} 07-21 16:57:24.055 25188 25202 I LifecycleTransaction: lifecycleStateRequest ResumeActivityItem{procState=-1,updateProcState=false,isForward=false} 07-21 16:57:24.056 11029 14482 I LifecycleTransaction: activityCallbacks ActivityConfigurationChange{config={0.0 ?mcc?mnc ?localeList ?layoutDir sw2147483647dp w960dp h264dp ?density nrml hdr widecg land ?uimode ?night -touch qwerty/v/v -nav/h winConfig={ mBounds=Rect(0, 0 - 1920, 720) mAppBounds=Rect(0, 0 - 1920, 624) mMaxBounds=Rect(0, 0 - 1920, 720) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0 mPopOverMode=0} userId:-10000 s.56 ?fontWeightAdjustment}} 07-21 16:57:24.056 1211 21300 I AudioFlinger: ThreadBase::exit 07-21 16:57:24.056 25188 25188 I SurfaceView: ViewUI 257196412 Changes: creating=false format=false size=false visible=true view_current=true mRequestedVisible=false alpha=false hint=false Alpha=false win=false win_current=true win_last=true position=false layout=false left=false top=false, Size: mReqW=498 mReqH=1080 w=498 h=1080 07-21 16:57:24.056 25188 25188 I SurfaceView: ViewUI 257196412 updateBoundsLayerCornerRadius isSetBefore=false isSet=false 07-21 16:57:24.056 25188 25188 I SurfaceView: ViewUI 257196412 surfaceDestroyed 分析为何ProjectionActivity relaunch了
07-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值