Android中保存和恢复Fragment状态的最好方法

关键点:Fragment的Arguments。

经过这几年使用Fragment之后,我想说,Fragment的确是一种充满智慧的设计,但是使用Fragment时有太多需要我们逐一解决的问题,尤其是在处理数据保持的时候。

首先,虽然其有类似于activity的onSaveInstanceState,但是别想仅仅靠onSaveInstanceState就能保持数据。

下面就是一些案例:

情景一:stack中只有一个Fragment的时候旋转屏幕
这里写图片描述

是的,旋转屏幕是测试数据保持最简单的方法。这种情况非常简单,你只需在onSaveInstanceState存储会在旋转的时候会丢失的数据,包括变量,然后在onActivityCreated或者onViewStateRestored中取出来:

int someVar;
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(someVar, someVar);
outState.putString(“text”, tv1.getText().toString());
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
someVar = savedInstanceState.getInt(someVar, 0);
tv1.setText(savedInstanceState.getString(“text”));
}
看起来很简单是吧,但是存在这样的情况,View重建,但是onSaveInstanceState未被调用,这意味着UI上的所有东西都丢失了,请看下面的案例。

情景2:Fragment从回退栈的返回
这里写图片描述

当fragment从backstack中返回(这里是Fragment A),根据 官方文档 对Fragment生命周期的描述,Fragment A中的view会重建。
这里写图片描述

从这张图可以看到,当Fragment从回退栈中返回的时候,onDestroyView 和 onCreateView被调用,但是onSaveInstanceState貌似没有被调用,这就导致了一切UI数据都回到了xml布局中定义的初始状态。当然,那些内部实现了状态保存的view,比如有android:freezeText属性的EditText和TextView,仍然可以保持其状态,因为Fragment可以为他们保持数据,但是开发者没法获得这些事件,我们只能手动的在onDestroyView中保存这些数据。

大概流程如下:

@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 这里保存数据
}
@Override
public void onDestroyView() {
super.onDestroyView();
// 如果onSaveInstanceState没被调用,这里也可以保存数据
}
但是问题来了onSaveInstanceState中保存数据很简单,因为它有Bundle参数,但是onDestroyView没有,那保存在哪里呢?答案是能和Fragment一起共存的Argument。

代码大致如下:

Bundle savedState;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Restore State Here
if (!restoreStateFromArguments()) {
// First Time running, Initialize something here
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save State Here
saveStateToArguments();
}
@Override
public void onDestroyView() {
super.onDestroyView();
// Save State Here
saveStateToArguments();
}
private void saveStateToArguments() {
savedState = saveState();
if (savedState != null) {
Bundle b = getArguments();
b.putBundle(“internalSavedViewState8954201239547”, savedState);
}
}
private boolean restoreStateFromArguments() {
Bundle b = getArguments();
savedState = b.getBundle(“internalSavedViewState8954201239547”);
if (savedState != null) {
restoreState();
return true;
}
return false;
}
/
// 取出状态数据
/
private void restoreState() {
if (savedState != null) {
//比如
//tv1.setText(savedState.getString(“text”));
}
}
//
// 保存状态数据
//
private Bundle saveState() {
Bundle state = new Bundle();
// 比如
//state.putString(“text”, tv1.getText().toString());
return state;
}

现在你可以轻松的在fragment的saveState和restoreState中分别存储和取出数据了。现在看起来好多了,几乎快要成功了,但是还有更极端的情况。

情景3:在回退栈中有一个以上的Fragment的时候旋转两次
这里写图片描述

当你旋转一次屏幕,onSaveInstanceState被调用,UI的状态会如预期的那样被保存,,但是当你再一次旋转屏幕,上面的代码就可能会崩溃。原因是虽然onSaveInstanceState被调用了,但是当你旋转屏幕,回退栈中Fragment的view将会销毁,同时在返回之前不会重建。这就导致了当你再一次旋转屏幕,没有可以保存数据的view。saveState()将会引用到一个不存在的view而导致空指针异常NullPointerException,因此需要先检查view是否存在。如果存在保存其状态数据,将Argument中的数据再次保存一遍,或者干脆啥也不做,因为第一次已经保存了。

private void saveStateToArguments() {
if (getView() != null)
savedState = saveState();
if (savedState != null) {
Bundle b = getArguments();
b.putBundle(“savedState”, savedState);
}
}

Fragment的最终模版:

下面是我正在使用的fragment模版。

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.inthecheesefactory.thecheeselibrary.R;

/**
* Created by nuuneoi on 11/16/2014.
*/
public class StatedFragment extends Fragment {

Bundle savedState;

public StatedFragment() {
    super();
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // Restore State Here
    if (!restoreStateFromArguments()) {
        // First Time, Initialize something here
        onFirstTimeLaunched();
    }
}

protected void onFirstTimeLaunched() {

}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    // Save State Here
    saveStateToArguments();
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    // Save State Here
    saveStateToArguments();
}


// Don't Touch !!


private void saveStateToArguments() {
    if (getView() != null)
        savedState = saveState();
    if (savedState != null) {
        Bundle b = getArguments();
        b.putBundle(internalSavedViewState8954201239547, savedState);
    }
}


// Don't Touch !!


private boolean restoreStateFromArguments() {
    Bundle b = getArguments();
    savedState = b.getBundle(internalSavedViewState8954201239547);
    if (savedState != null) {
        restoreState();
        return true;
    }
    return false;
}

/
// Restore Instance State Here
/

private void restoreState() {
    if (savedState != null) {
        // For Example
        //tv1.setText(savedState.getString(text));
        onRestoreState(savedState);
    }
}

protected void onRestoreState(Bundle savedInstanceState) {

}

//
// Save Instance State Here
//

private Bundle saveState() {
    Bundle state = new Bundle();
    // For Example
    //state.putString(text, tv1.getText().toString());
    onSaveState(state);
    return state;
}

protected void onSaveState(Bundle outState) {

}

}
如果你使用这个模版,你只需继承StatedFragment类然后在onSaveState()保存数据,在onRestoreState()中取出数据,其余的事情上面的代码已经为你做好了,我相信覆盖了我所知道的所有情况。

现在本文描述的StatedFragment已经被做成了一个易于使用的库,并且发布到了jcenter,你现在只需在build.gradle中添加依赖就行了:

dependencies {
compile ‘com.inthecheesefactory.thecheeselibrary:stated-fragment-support-v4:0.9.1’
}
继承StatedFragment,同时分别在onSaveState(Bundle outState)和onRestoreState(Bundle savedInstanceState)中保存和取出状态数据。如果你想在fragment第一次启动的时候做点什么,你也可以重写onFirstTimeLaunched(),它只会在第一次启动的时候被调用。

public class MainFragment extends StatedFragment {

...

/**
 * Save Fragment's State here
 */
@Override
protected void onSaveState(Bundle outState) {
    super.onSaveState(outState);
    // For example:
    //outState.putString(text, tvSample.getText().toString());
}

/**
 * Restore Fragment's State here
 */
@Override
protected void onRestoreState(Bundle savedInstanceState) {
    super.onRestoreState(savedInstanceState);
    // For example:
    //tvSample.setText(savedInstanceState.getString(text));
}

...

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Fragment 是一种可以嵌入到 Activity 的可重用组件。当 Activity 运行时,Fragment 可以独立地添加、移除、替换和交互。下面是 Fragment 的执行过程: 1. 创建 Fragment 对象:通过调用 Fragment 的构造函数来创建一个新的 Fragment 对象。 2. 添加 Fragment 到 Activity:使用 FragmentManager 将 Fragment 添加到对应的 Activity。 3. 为 Fragment 创建视图:在 Fragment 的生命周期方法 onCreateView() 创建和返回视图层次结构。 4. 绑定 Fragment 到 Activity:在 Fragment 的生命周期方法 onAttach() Fragment 绑定到它的父 Activity。 5. 创建 Fragment:在 Fragment 的生命周期方法 onCreate() 进行初始化工作,例如恢复保存状态、初始化变量等。 6. 创建 Activity 视图:在 Activity 的生命周期方法 onCreateView() 创建 Activity 的视图层次结构。 7. 把 Fragment 加入到 Activity 视图:将 Fragment 在 Activity 视图占据的位置预留给它,可以使用 <fragment> 标签或者动态添加。 8. 可见性改变:在 Fragment 的生命周期方法 onStart() Fragment 变为可见状态,可以与用户交互。 9. 焦点获取:在 Fragment 的生命周期方法 onResume() Fragment 获取焦点,可以响应用户输入。 10. 用户交互:Fragment 可以通过用户输入(点击按钮、触摸屏幕等)响应用户交互,处理用户交互事件。 11. 生命周期变化:当 Activity 的生命周期发生变化时,Fragment 的生命周期也会随之变化。 12. 可见性改变:在 Fragment 的生命周期方法 onPause() Fragment 失去焦点,不再与用户交互。 13. 保存状态:在 Fragment 的生命周期方法 onSaveInstanceState() 保存 Fragment状态。 14. 不可见性改变:在 Fragment 的生命周期方法 onStop() Fragment 变为不可见状态。 15. 解绑 Fragment:在 Fragment 的生命周期方法 onDetach() ,将 Fragment 与它的父 Activity 解绑。 16. 销毁 Fragment:在 Fragment 的生命周期方法 onDestroy() ,释放资源,销毁 Fragment 对象。 这是一个典型的 Fragment 生命周期流程,每个生命周期方法都提供了对应的回调函数,我们可以在这些方法执行相应的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值