zoukankan      html  css  js  c++  java
  • Android中保存和恢复Fragment状态的最好方法 分类: Android 2015-07-29 13:59 103人阅读 评论(0) 收藏

    英文原文: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));
        }
      
        ...
      
    }

  • 相关阅读:
    lr 增强窗格中,如何生成调试信息?
    lr 自带的例子,如何进行关联,通过代码的函数进行实现
    lr11 录制脚本时候,无法自动启动ie,查了网上很多方法都未解决?
    loadrunner11 录制脚步不成功,在录制概要出现“No Events were detected”,浮动窗口总是显示“0 Events”,解决办法
    loadrunner11 安装及破解教程来自百度文库
    安装loadrunner11 ,出现如下错误如何解决?
    回收站数据删除了,如何进行恢复?
    网管工作方面——————打印机删除了然后开机重启他依然存在,如何解决
    Windows 不能在 本地计算机 启动 SQL Server 服务 错误代码126
    Sorry, the page you are looking for is currently unavailable. Please try again later. Nginx
  • 原文地址:https://www.cnblogs.com/xieping/p/4714161.html
Copyright © 2011-2022 走看看