您的位置:首页 > 移动开发 > Android开发

Android中保存和恢复Fragment状态最佳方案

2015-04-29 09:20 435 查看
英文原文:Probably be the best way (?) to save/restore Android Fragment’s state so far

关键点:Fragment的Arguments。

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

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

下面就是一些案例:

情景一:stack中只有一个Fragment的时候旋转屏幕



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

[js] view
plaincopy

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中保存这些数据。

大概流程如下:

[js] view
plaincopy

@Override

public void onSaveInstanceState(Bundle outState) {

super.onSaveInstanceState(outState);

// 这里保存数据

}

@Override

public void onDestroyView() {

super.onDestroyView();

// 如果onSaveInstanceState没被调用,这里也可以保存数据

}

但是问题来了onSaveInstanceState中保存数据很简单,因为它有Bundle参数,但是onDestroyView没有,那保存在哪里呢?答案是能和Fragment一起共存的Argument

代码大致如下:

[js] view
plaincopy

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中的数据再次保存一遍,或者干脆啥也不做,因为第一次已经保存了。

[js] view
plaincopy

private void saveStateToArguments() {

if (getView() != null)

savedState = saveState();

if (savedState != null) {

Bundle b = getArguments();

b.putBundle(“savedState”, savedState);

}

}


Fragment的最终模版:

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

[js] view
plaincopy

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中添加依赖就行了:

[js] view
plaincopy

dependencies {

compile 'com.inthecheesefactory.thecheeselibrary:stated-fragment-support-v4:0.9.1'

}

继承StatedFragment,同时分别在
onSaveState(Bundle outState)
onRestoreState(Bundle savedInstanceState)
中保存和取出状态数据。如果你想在fragment第一次启动的时候做点什么,你也可以重写
onFirstTimeLaunched(),它只会在第一次启动的时候被调用。


[js] view
plaincopy

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"));

}



...



}

首发地址 http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0327/2648.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐