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

英文原文: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中取出来:

[js]  view plain copy
  1. int someVar;  
  2. @Override  
  3. protected void onSaveInstanceState(Bundle outState) {  
  4.    outState.putInt("someVar", someVar);  
  5.    outState.putString(“text”, tv1.getText().toString());  
  6. }  
  7. @Override  
  8. public void onActivityCreated(Bundle savedInstanceState) {  
  9.    super.onActivityCreated(savedInstanceState);  
  10.    someVar = savedInstanceState.getInt("someVar", 0);  
  11.    tv1.setText(savedInstanceState.getString(“text”));  
  12. }  

看起来很简单是吧,但是存在这样的情况,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中保存这些数据。

大概流程如下:

[js]  view plain copy
  1. @Override  
  2. public void onSaveInstanceState(Bundle outState) {  
  3.    super.onSaveInstanceState(outState);  
  4.    // 这里保存数据  
  5. }  
  6. @Override  
  7. public void onDestroyView() {  
  8.    super.onDestroyView();  
  9.    // 如果onSaveInstanceState没被调用,这里也可以保存数据  
  10. }  

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

代码大致如下:

[js]  view plain copy
  1. Bundle savedState;  
  2. @Override  
  3. public void onActivityCreated(Bundle savedInstanceState) {  
  4.    super.onActivityCreated(savedInstanceState);  
  5.    // Restore State Here  
  6.    if (!restoreStateFromArguments()) {  
  7.       // First Time running, Initialize something here  
  8.    }  
  9. }  
  10. @Override  
  11. public void onSaveInstanceState(Bundle outState) {  
  12.    super.onSaveInstanceState(outState);  
  13.    // Save State Here  
  14.    saveStateToArguments();  
  15. }  
  16. @Override  
  17. public void onDestroyView() {  
  18.    super.onDestroyView();  
  19.    // Save State Here  
  20.    saveStateToArguments();  
  21. }  
  22. private void saveStateToArguments() {  
  23.    savedState = saveState();  
  24.    if (savedState != null) {  
  25.       Bundle b = getArguments();  
  26.       b.putBundle(“internalSavedViewState8954201239547”, savedState);  
  27.    }  
  28. }  
  29. private boolean restoreStateFromArguments() {  
  30.    Bundle b = getArguments();  
  31.    savedState = b.getBundle(“internalSavedViewState8954201239547”);  
  32.    if (savedState != null) {  
  33.       restoreState();  
  34.       return true;  
  35.    }  
  36.    return false;  
  37. }  
  38. /  
  39. // 取出状态数据  
  40. /  
  41. private void restoreState() {  
  42.    if (savedState != null) {  
  43.       //比如  
  44.       //tv1.setText(savedState.getString(“text”));  
  45.    }  
  46. }  
  47. //  
  48. // 保存状态数据  
  49. //  
  50. private Bundle saveState() {  
  51.    Bundle state = new Bundle();  
  52.    // 比如  
  53.    //state.putString(“text”, tv1.getText().toString());  
  54.    return state;  
  55. }  

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

 

情景3:在回退栈中有一个以上的Fragment的时候旋转两次

1-UruQA80WVoyaVQGxbZYE1w

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

[js]  view plain copy
  1. private void saveStateToArguments() {  
  2.    if (getView() != null)  
  3.       savedState = saveState();  
  4.    if (savedState != null) {  
  5.       Bundle b = getArguments();  
  6.       b.putBundle(“savedState”, savedState);  
  7.    }  
  8. }  


Fragment的最终模版:

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

[js]  view plain copy
  1. import android.os.Bundle;  
  2. import android.support.v4.app.Fragment;  
  3. import android.view.LayoutInflater;  
  4. import android.view.View;  
  5. import android.view.ViewGroup;  
  6.    
  7. import com.inthecheesefactory.thecheeselibrary.R;  
  8.    
  9. /** 
  10.  * Created by nuuneoi on 11/16/2014. 
  11.  */  
  12. public class StatedFragment extends Fragment {  
  13.    
  14.     Bundle savedState;  
  15.    
  16.     public StatedFragment() {  
  17.         super();  
  18.     }  
  19.    
  20.     @Override  
  21.     public void onActivityCreated(Bundle savedInstanceState) {  
  22.         super.onActivityCreated(savedInstanceState);  
  23.         // Restore State Here  
  24.         if (!restoreStateFromArguments()) {  
  25.             // First Time, Initialize something here  
  26.             onFirstTimeLaunched();  
  27.         }  
  28.     }  
  29.    
  30.     protected void onFirstTimeLaunched() {  
  31.    
  32.     }  
  33.    
  34.     @Override  
  35.     public void onSaveInstanceState(Bundle outState) {  
  36.         super.onSaveInstanceState(outState);  
  37.         // Save State Here  
  38.         saveStateToArguments();  
  39.     }  
  40.    
  41.     @Override  
  42.     public void onDestroyView() {  
  43.         super.onDestroyView();  
  44.         // Save State Here  
  45.         saveStateToArguments();  
  46.     }  
  47.    
  48.       
  49.     // Don't Touch !!  
  50.       
  51.    
  52.     private void saveStateToArguments() {  
  53.         if (getView() != null)  
  54.             savedState = saveState();  
  55.         if (savedState != null) {  
  56.             Bundle b = getArguments();  
  57.             b.putBundle("internalSavedViewState8954201239547", savedState);  
  58.         }  
  59.     }  
  60.    
  61.       
  62.     // Don't Touch !!  
  63.       
  64.    
  65.     private boolean restoreStateFromArguments() {  
  66.         Bundle b = getArguments();  
  67.         savedState = b.getBundle("internalSavedViewState8954201239547");  
  68.         if (savedState != null) {  
  69.             restoreState();  
  70.             return true;  
  71.         }  
  72.         return false;  
  73.     }  
  74.    
  75.     /  
  76.     // Restore Instance State Here  
  77.     /  
  78.    
  79.     private void restoreState() {  
  80.         if (savedState != null) {  
  81.             // For Example  
  82.             //tv1.setText(savedState.getString("text"));  
  83.             onRestoreState(savedState);  
  84.         }  
  85.     }  
  86.    
  87.     protected void onRestoreState(Bundle savedInstanceState) {  
  88.    
  89.     }  
  90.    
  91.     //  
  92.     // Save Instance State Here  
  93.     //  
  94.    
  95.     private Bundle saveState() {  
  96.         Bundle state = new Bundle();  
  97.         // For Example  
  98.         //state.putString("text", tv1.getText().toString());  
  99.         onSaveState(state);  
  100.         return state;  
  101.     }  
  102.    
  103.     protected void onSaveState(Bundle outState) {  
  104.    
  105.     }  
  106. }  

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

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

[js]  view plain copy
  1. dependencies {  
  2.     compile 'com.inthecheesefactory.thecheeselibrary:stated-fragment-support-v4:0.9.1'  
  3. }  

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

[js]  view plain copy
  1. public class MainFragment extends StatedFragment {  
  2.    
  3.     ...  
  4.    
  5.     /** 
  6.      * Save Fragment's State here 
  7.      */  
  8.     @Override  
  9.     protected void onSaveState(Bundle outState) {  
  10.         super.onSaveState(outState);  
  11.         // For example:  
  12.         //outState.putString("text", tvSample.getText().toString());  
  13.     }  
  14.    
  15.     /** 
  16.      * Restore Fragment's State here 
  17.      */  
  18.     @Override  
  19.     protected void onRestoreState(Bundle savedInstanceState) {  
  20.         super.onRestoreState(savedInstanceState);  
  21.         // For example:  
  22.         //tvSample.setText(savedInstanceState.getString("text"));  
  23.     }  
  24.    
  25.     ...  
  26.    
  27. }  

首发地址  http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0327/2648.html  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值