这是Android Jetpack 系列文章的第一篇,主要是总结一下在学习Jetpack组件库时遇到的各种问题和学习心得。
Google官方于17/18 IO大会上推出了Jetpack。Jetpack是一个组件库,包含了四大组件,以及四大组件下的很多小的模块。这些模块都是可以单独使用的,你可以直接引入其中一些成熟的模块来重构项目,这样就非常灵活。
Android Jetpack 的四大组件
这些组件包括:
Data Bingding(数据绑定)
Room(数据库)
WorkManager(后台任务管家)
Lifecycle(生命周期)
Navigation(导航)
Paging(分页)
Data Binding(数据绑定)
LiveData(底层数据通知更改视图)
ViewModel(以注重生命周期的方式管理界面的相关数据)
Download manager(下载给管理器)
Media & playback(媒体和播放)
Notifications(通知)
Permissions(权限)
Preferences(偏好设置)
Sharing(共享)
Slices(切片)
Animation & transitions(动画和过渡)
Auto(Auto组件)
Emoji(标签)
Fragment(Fragment)
Layout(布局)
Palette(调色板)
TV(TV)
Wear OS by Google(穿戴设备)
今天我从ViewModel开始介绍JetPack组件。
ViewModel是JetPack中用于实现UI数据持久化的一个模块。我们在开发的时候,经常会遇到一个问题,UI层的数据不好管理——因为有时Activity和Fragment的生命周期是不可控的。如果Activity销毁了,那数据就没了;又或者是旋转屏幕方向的时候数据的保存问题。虽然有 onSaveInstanceState() 这样的方法可以调用,但总归是不太灵活。
为此,ViewModel设计了特殊的生命周期:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0IYWxyF6-1573137872405)( https://upload-images.jianshu.io/upload_images/9764942-f2b3f9b162334ff0.png )]
如图所示,ViewModel的生命周期可以贯穿Activity的全过程,真正实现了对UI数据的妥善保存。
在开发中我们对网络的请求大多是异步进行的,如果Activity被销毁了数据才回调,那很有可能造成空指针错误。我们可以把请求回来的数据保存在ViewModel中,这样就可以实现对数据的解耦,避免了这个问题。
同一个Activity下的不同Fragment之间经常需要共享数据,如果回调来回调去的,或者你持有我我持有你的实例,就会造成很多麻烦。ViewModel就很好的解决了这个问题。
下面我用一个具体的例子来介绍ViewModel的简单使用。这个demo的结构是一个Activity中有两个Fragment,分别是 FirstFragment 和 SecondFragment。demo很简单,两个 fragemnt 共享父 Activity 的 ViewModel ,都有一个 +1 的button,同时也都有一个跳转到另一个fragment的button。一个TextView负责显示当前计数器的数值。
Demo的界面
首先是ViewModelActivity,这是demo的主入口:
public class ViewModelActivity extends AppCompatActivity {
private FragmentManager fragmentManager = getSupportFragmentManager();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model);
}
@Override
protected void onStart() {
super.onStart();
// 加载Fragment
fragmentManager.beginTransaction().replace(R.id.fragment, new FirstFragment()).commit();
}
}
FirstFragment:
public class FirstFragment extends Fragment {
private TextView textView;
private Button add;
private Button jump;
private MyViewModel myViewModel;
public FirstFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//获取ViewModel实例
myViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
Log.d("model", String.valueOf(myViewModel));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_first, container, false);
add = view.findViewById(R.id.button_add);
jump = view.findViewById(R.id.button_next);
textView = view.findViewById(R.id.count_text);
return view;
}
@Override
public void onStart() {
super.onStart();
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//调用setCount方法加1
myViewModel.setCount(1);
}
});
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getFragmentManager().beginTransaction().replace(R.id.fragment, new SecondFragment()).commit();
}
});
myViewModel.getCountLiveData().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
}
}
SecondFragment也是类似的:
public class SecondFragment extends Fragment {
private TextView textView;
private Button add;
private Button jump;
private MyViewModel myViewModel;
public SecondFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_second, container, false);
add = view.findViewById(R.id.button_add);
jump = view.findViewById(R.id.button_next);
textView = view.findViewById(R.id.count_text);
return view;
}
@Override
public void onStart() {
super.onStart();
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.setCount(1);
}
});
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getFragmentManager().beginTransaction().replace(R.id.fragment, new FirstFragment()).commit();
}
});
myViewModel.getCountLiveData().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
}
获取ViewModel实例那里,使用 ViewModelProviders
这个命令可以获取一个 ViewModel 实例,这个实例是对应于 Activity 的,对于一个ViewModel Class 会有唯一的一个实例。在这个Activity的生命周期中,这个ViewModel会一直存活。
ViewModel的类:
/**
* Created By Diao Su
* Date 2019/11/4
*/
public class MyViewModel extends ViewModel {
private int VOLUME = 0;
private final MutableLiveData<Integer> countLiveData = new MutableLiveData<>();
public void setCount(int count) {
countLiveData.postValue(VOLUME += count);
}
public MutableLiveData<Integer> getCountLiveData() {
return countLiveData;
}
}
可以看到,这个MyViewModel是继承自ViewModel的,定义一个int型变量 VOLUME 来存放目前计数器的值,新建一个Integer型的LiveData来存放VOLUME。一般来说,LiveData是配合着ViewModel使用的,其构造函数的泛型决定了LiveData能存放什么数据。
此外,我们还定义了setCount
和getCountLiveData
两个方法,用以设置VOLUME和取出存放VOLUME的LiveData。那么为什么不能直接取出VOLUME而是要返回一个LiveData呢?读到后面你就知道了~
注意以下这段fragment中的代码:
myViewModel.getCountLiveData().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
这里利用了viewmodel封装好的一个方法,实现了观察者模式。我们不用在意实现,就能很方便的在onChanged回调函数里取出当前最新的VOLUME值。这也符合JetPack的设计初衷之一 —— 就是我们不用去关心很多组件的实现或者生命周期balabala,我们只用去关注怎么使用JetPack给我们提供的便利完成高质量的项目。
运行的效果是,两个fragment就像是在同一个界面中一样,可以同时操作计数器:
以上就是JetPack系列的第一期啦~用好ViewModel+LiveData可以给我们的项目开发带来非常多的便利。下一期我将继续学习Room组件。