IcePick——这是我在网上搜集资料的时候偶然发现的一个框架。作为一个与ButterKnife类似的简化开发框架,可能是由于其功能的局限性,使用百度搜索这个框架时,并没有过多的介绍资料。出于对使用冷门框架的好奇性,于是在本篇博客中简单介绍一下IcePick这个框架,以及其最新的版本3.2所提供的新功能——构建定制的Bundle。
IcePick已在3年前就停止了更新,也算是趋于比较稳定的版本了,官网上导入依赖的语句还是已经过时的compile
和provided
。使用这个框架之前,应该先去查看一下它的GitHub官网介绍:IcePick。对于它的功能介绍,我们放在下一节中。
在一个Android应用中,遇到某些场景,我们往往需要手动去保存Activity或者Fragment的状态,例如我们在玩王者荣耀的时候,突然来了一个电话,接听完电话之后我们返回到游戏中,这个时候我们希望游戏还是之前那个进度(谁都不想一返回游戏就看到一个大大的Defeat吧),那么我们就需要将其状态保存起来,这样在其被摧毁时,我们还能够根据保存的状态回到之前的进度。
一般来说,保存信息使用的是Bundle类型的对象,而与保存状态有关的方法是:
onSaveInstanceState()
:如果调用, 则会发生在onPause()
或onStop()
方法之前。onRestoreInstanceState()
:如果调用,则会发生在onStart
和onResume
方法之间。只有在Activity被系统回收,重新创建Activity的情况下才会被调用。注意,若onRestoreInstanceState()被调用,onSaveInstanceState()必也被调用,但反之不亦。若想深入研究的读者可以参考此篇博客:浅析onRestoreInstanceState调用时机这里我们稍微复习一下Activity(这里主要分析Activity)保存数据的业务逻辑:
onSaveInstanceState()
或者onRestoreInstanceStat()
的也就是说,我们这里讨论的状态保存,更多是出于异常状态下的。
为了保存状态,传统的做法是,通过重写onSaveInstanceState()
方法里的实现,将某些数据添加到Bundle对象中,然后再在onCreate()
中传入的Bundle对象中取出数据即可。这种做法适用于需要存储数据量较少的情况,若数据量过多,则需要写很多的重复代码去保存和回复数据,很耗费时间。
正因为产生了这种效率问题,IcePick便应运而生。跟ButterKnife类似,该框架同样适用了注解的形式去解决保存状态的问题,让我们正式开始使用它吧!
鉴于IcePick在GitHub上的依赖导入存在一点问题(下面会贴出问题的所在),这里给出作者本人导入的相关依赖,修改moudle下的build.gradle,代码如下:
implementation 'frankiesardo:icepick:3.2.0'
compileOnly 'frankiesardo:icepick-processor:3.2.0'
annotationProcessor 'frankiesardo:icepick-processor:3.2.0'
另外:原本官网上是没有第三行依赖,即annotationProcessor 'frankiesardo:icepick-processor:3.2.0'
,但是若不添加这行,就会报出如图所示的错误:
原因:根据Google提供的官方文档:如果添加了注释编译器(即compileOnly
),就需要在dependencies中增加annotationProcessor 依赖内容
,Google官网原文如下:
解决方法:添加上文中所提及的全部依赖即可。
布局文件比较简单,只需要提供一个按钮和一个文本控件即可,代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/btn_add_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按一次加一"/>
<TextView
android:id="@+id/tv_number"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="100sp"
android:gravity="center_horizontal"/>
LinearLayout>
现在想要实现这样一个功能:每次点击一下按钮,文本就充当计数器的角色,显示 + 1,假设现在文本上已经显示到了10。当我们让Actvitiy处于异常状况时(这里就使用最简单的翻转让Activity销毁然后又创建),希望Activity上的文本控件依旧显示10,就需要调用onSaveInstanceState()
了,而不使用onRestoreInstanceState()
的原因是因为在Activity的创建方法onCreate()
中不会调用它,所以只能调用onSaveInstanceState()
了,MainActivity代码如下:
public class MainActivity extends AppCompatActivity {
private Button btn_add_number;
private TextView tv_number;
@State
int mNumber = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 使用IcePick读取数据
Icepick.restoreInstanceState(this, savedInstanceState);
// 获取控件实例
btn_add_number = findViewById(R.id.btn_add_number);
tv_number = findViewById(R.id.tv_number);
// 初始化文本控件
tv_number.setText(mNumber + "");
// 为按钮注册点击事件
btn_add_number.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv_number.setText(++mNumber + "");
}
});
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
// 使用IcePick存入数据
Icepick.saveInstanceState(this, outState);
super.onSaveInstanceState(outState);
}
}
写到这里,你可以开启模拟器去测试一下,无论你以怎样的异常情况结束了当前Activity,当再创建的时候,mNumber
总是会等于原来的那个值。
简单总结一下,IcePick的使用步骤:
@State
注解,表示被IcePick进行管理onSaveInstanceState()
中调用Icepick.saveInstanceState(this, outState);
来存储状态onCreate()
中调用Icepick.restoreInstanceState(this, savedInstanceState);
来恢复状态怎么样,是不是很简单?相比于传统的get/put
调用,这三个步骤明显要简化了许多,从此不再需要写繁琐的重复代码,节省了大量的时间。
注意:加上了@State
的字段,不能使用private
、static
、final
等修饰符,否则会报出如图所示的错误:
这个新功能是我在查看IcePick的官方文档中发现的,原文如下:
From version 3.2.0 you can supply a class parameter to the State annotation. This class should implement the Bundler interface and you can use it to provide custom serialisation and deserialisation for your types.
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) { ... } }
This is useful if you want to use icepick in conjunction with Parceler.
大致意思是,通过IcePick,可以构造一个自定义的Bundle,从而实现某种定制的存储结构。由于并非主要功能,有兴趣的读者可以研究一下这个demo,作者这里就不再演示。
当然,IcePick的功能还不止这些,想要深入研究的还是那句老话:查看官方文档,没有任何教程要比官方文档详细了。
AFL——Android框架学习