• 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));
        }
      
        ...
      
    }

  • 相关阅读:
    Identity Server 4 从入门到落地(六)—— 简单的单页面客户端
    Identity Server 4 从入门到落地(十一)—— Docker部署
    C# 脚本
    网站迁移纪实:从Web Form 到 Asp.Net Core (Abp vNext 自定义开发)
    Identity Server 4 从入门到落地(七)—— 控制台客户端
    Robot Framework 使用总结
    Asp.Net Core 使用Monaco Editor 实现代码编辑器
    Identity Server 4 从入门到落地(五)—— 使用Ajax访问Web Api
    C# RabbitMQ的使用
    创建VS Code 扩展插件
  • 原文地址:https://www.cnblogs.com/xieping/p/4714161.html
Copyright © 2020-2023  润新知