bundle 屏幕切换保存数据

转载 2016年08月31日 11:36:39

英文原文:Probably be the best way (?) to save/restore Android Fragment’s state so far

关键点:Fragment的Arguments。

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

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

下面就是一些案例:

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

1-kV1CcEEFC_upnM-5Mn77HA

是的,旋转屏幕是测试数据保持最简单的方法。这种情况非常简单,你只需在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从回退栈的返回

1-FmcbQAjUusX5qY8F8N-1Iw

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

1-kbK7DckgeJiBgpGFQGbcog

 

从这张图可以看到,当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的时候旋转两次

1-UruQA80WVoyaVQGxbZYE1w

当你旋转一次屏幕,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));
    }
 
    ...
 
}

android bundle存放数据详解

正如大家所知道,Activity之间传递数据,是将数据存放在Intent或者Bundle中 例如: 将数据存放倒Intent中传递: 将数据放到Bundle中传递: 但是Int...
  • h183288132
  • h183288132
  • 2015年11月07日 14:49
  • 1773

android之纵横屏幕切换时保存数据

java代码: package com.sxt.day04_05; import java.io.IOException; import java.io.InputStream; im...
  • qa962839575
  • qa962839575
  • 2014年11月27日 15:50
  • 1009

安卓保存数据之onsaveInstanceStace(Bundle outState)的使用

当安卓设备配置【一系列特征的组合:屏幕方向,屏幕像素密度,屏幕尺寸,键盘类型,底座模式,以及语言等】发生改变时,可能会有更合适的资源来匹配新的设备环境。 于是,Android会销毁当前activity...
  • dummyo
  • dummyo
  • 2018年01月03日 14:37
  • 155

android:活动销毁时保存数据

当onstop状态的activity被销毁时如何保存数据================================================ 方法一: 重载onSaveInstanceS...
  • C_Creator
  • C_Creator
  • 2016年08月11日 00:04
  • 673

Android 使用bundle在activity之间交换数据

在android应用中,经常会有多个activity,而这些activity之间又经常需要交换数据。这时,可以将要保存的数据存放在bundle对象中,然后通过intent提供的putExtras()方...
  • hellokandy
  • hellokandy
  • 2016年07月03日 22:50
  • 851

Pro Android学习笔记(四一):Fragment(6):数据保留

在Fragment的生命周期中的Bundle savedInstanceState是用于保存fragment状态,而不是fragment的相关数据。我们今天要解决的问题是,通过fragment,横竖屏...
  • flowingflying
  • flowingflying
  • 2013年10月15日 14:25
  • 24781

Android之屏幕切换使用技巧

Android之屏幕切换使用技巧文章链接:知识点: android:screenOrientation及属性介绍; 屏幕切换时加载不同布局; setRequestedOrientation手动设置布局...
  • qq_16628781
  • qq_16628781
  • 2017年04月21日 12:34
  • 946

Android 屏幕切换效果实现 (转)

Android 屏幕切换效果实现 (转) 本教程将介绍如何实现屏幕间的切换效果。在前述Ophone 2D UI 动画教程 中介绍了OPhone提供的animation功能,里面介绍了如...
  • lua1420
  • lua1420
  • 2011年06月29日 15:09
  • 3349

android屏幕切换

在以前的版本中只要在AndroidManifest.xml文件中对activity指定android:configChanges="keyboardHidden|orientation"属性,转屏的时...
  • fwt336
  • fwt336
  • 2015年01月26日 11:52
  • 326

Android屏幕切换

为了避免切屏时Activity重启,我们平时做法是在manifest的activity节点设置 android:name=".MyActivity" android:c...
  • u011370871
  • u011370871
  • 2016年04月28日 16:11
  • 184
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:bundle 屏幕切换保存数据
举报原因:
原因补充:

(最多只允许输入30个字)