Android视图数据保存和恢复的最佳方案

相信大家都遇到过,activity中展示的数据,在经历屏幕切换或者放在后台一会,
再打开,界面中的数据就被回收掉,数据显示不正确的情况。

相信大家的处理方式都是在onSaveInstanceState里面保存需要保存的数据,然后再
onCreate 或者 onRestoreInstanceState(onStart onResume方法之间执行) 再取出对应的数据来做恢复操作。

相信大家,如果遇上n多界面,都会这样写很麻烦吧?有没有更好的解决方案呢,
本篇文章主要就讲解关于数据保存与恢复的最佳处理方案。


待处理示例

需要保存的数据:比如从上个界面传递过来的购物数量,此数量可以在当前界面进行增加和删除功能。

  • 不进行数据保存与恢复的写法
    • 当我们 触发btn_add事件后,num数一直在改变
    • 如果此时屏幕切换将重新执行onCreate方法,此时num数将等于getIntent().getIntExtra(“NUM”,0);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      private int num;
      private Button btn_add;
      private TextView txt_num;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      num = getIntent().getIntExtra("NUM",0);
      txt_num = findViewById(R.id.txt_num);
      txt_num.setText(""+num);
      btn_add = findViewById(R.id.btn_add);
      btn_add.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
      num++;
      txt_num.setText(""+num);
      }
      });
      }
      对于这种问题,我们明显需要处理数据的保存于恢复,接着看下面两种方式

普通解决方案

  • 在onSaveInstanceState方法中保存num
  • 在onCreate方法中获取savedInstanceState中保存的num
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    private int num;
    private Button btn_add;
    private TextView txt_num;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    num = getIntent().getIntExtra("NUM",0);
    if(savedInstanceState!=null){
    num = savedInstanceState.getInt("NUM",num);
    }
    txt_num = findViewById(R.id.txt_num);
    txt_num.setText(""+num);
    btn_add = findViewById(R.id.btn_add);
    btn_add.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View view) {
    num++;
    txt_num.setText(""+num);
    }
    });
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("NUM",num);
    super.onSaveInstanceState(outState);
    }
    可能数据少还比较能接受,但是要保存的参数越来越多怎么办,outState.putXX
    和 savedInstanceState.getXX 则会越来越多,看看下面的解决方案吧。

    icepick解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @State int num;
    private TextView txt_lable;
    private Button btn_add;
    private TextView txt_num;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    num = getIntent().getIntExtra("NUM", 0);
    Icepick.restoreInstanceState(this, savedInstanceState);
    txt_num = findViewById(R.id.txt_num);
    txt_num.setText("" + num);
    btn_add = findViewById(R.id.btn_add);
    btn_add.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View view) {
    num++;
    txt_num.setText("" + num);
    }
    });
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
    }
    关键代码:
  • Icepick.restoreInstanceState(this, savedInstanceState); 从savedInstanceState获取存在的值赋值给@State标记过的数据
  • Icepick.saveInstanceState(this, outState); 缓存@State标记过的数据

对于上述所说的多参数,还会有很多get 和 put 的臃肿代码么?
很明显,如果多一个参数,我们只需要在参数前面加一个 @State 标记就够了,
简单吧,下面可以接着看看更多icepick关于的介绍

icepick使用介绍

icepick 是什么

icepick 是用于数据保存与恢复,避免多写模板代码而生的一个开源库,开源地址 https://github.com/frankiesardo/icepick
原理其实和黄油刀一样,怎么样,可以少写很多代码吧!

如何使用

导入依赖

1
2
3
4
5
6
7
repositories {
maven {url "https://clojars.org/repo/"}
}
dependencies {
compile 'frankiesardo:icepick:3.1.0'//gradle高版本中用 implementation 'frankiesardo:icepick:3.1.0'
provided 'frankiesardo:icepick-processor:3.1.0'// annotationProcessor 'frankiesardo:icepick-processor:3.1.0'
}

代码使用

上面其实已经讲解了主要用法

  • @State 标记需要保存恢复的数据
  • 提示,对于恢复和保存可以放在父类中
    • 在需要恢复数据的地方调用 Icepick.restoreInstanceState(this, savedInstanceState)
    • 在需要保存数据的地方调用 Icepick.saveInstanceState(this, outState);
  • 主要用在activity fragment view 类中
  • activity 和 fragment
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class ExampleActivity extends Activity {// Fragment
    @State String username; // This will be automatically saved and restored

    @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
    }

    @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
    }

    // You can put the calls to Icepick into a BaseActivity
    // All Activities extending BaseActivity automatically have state saved/restored
    }
  • View
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class CustomView extends View {
    @State int selectedPosition; // This will be automatically saved and restored

    @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
    }

    @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
    }

    // You can put the calls to Icepick into a BaseCustomView and inherit from it
    // All Views extending this CustomView automatically have state saved/restored
    }

注意事项

  • @State 以上写法,只能标记基本数据类型的数据

  • 如何标记自定义数据类型呢?方式如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class MyFragment {
    @State(MyCustomBundler.class) MyCustomType myCustomType;
    }

    class MyCustomBundler implements Bundler<MyCustomType> {
    void put(String key, MyCustomType value, Bundle bundle) {
    ...
    }

    MyCustomType get(String key, Bundle bundle) {
    ...
    }
    }

    混淆相关

    1
    2
    3
    4
    5
    6
    7
    -dontwarn icepick.**
    -keep class icepick.** { *; }
    -keep class **$$Icepick { *; }
    -keepclasseswithmembernames class * {
    @icepick.* <fields>;
    }
    -keepnames class * { @icepick.State *;}

    相关参考资料