前言
学习记录系列是通过阅读学习《Android Jetpack应用指南》对书中内容学习记录的Blog,《Android Jetpack应用指南》京东天猫有售,本文是学习记录的第五篇。
简介
LiveData(实时数据) 是一个可被观察的数据容器类。具体说来,可以将 LiveData 理解为一个数据的容器,它将数据包装起来,使数据包装起来,使数据成为被观察者,当该数据发生变化时,观察者能够获得通知。我们不需要自己去实现观察者模式, LiveData 内部已经默认实现,只要使用就可以了。
LiveData 和 ViewModel 的关系
1、ViewModel 用于存放页面所需要的各种数据,不仅如此,还可以在其中放一些与数据相关的业务逻辑。例如,可以在 ViewModel 中进行数据的加工,获取等操作。因此,ViewModel 中的数据可能会随着业务的变化而变化。
2、对页面来说,它并不关心 ViewModel 中的业务逻辑,它只关心需要展示的数据是什么,并且希望在数据发生变化时,能及时得到通知并做出更新。LiveData 的作用就是,在 ViewModel 中的数据发生变化时通知页面。因此,LiveData 通常被放在 ViewModel 中使用, 用于包装 ViewModel 中那些需要被外界观察的数据。
LiveData 的基本使用方法
在第 4 章 ViewMoedl 的计时器案例的基础上,使用 LiveData 对接口进行改写
1.LiveData 是一个抽象类,不能直接使用。通常使用的是它的直接直接子类 MutableLiveData
/**
* @author JinXin 2020/11/3
*/
public class TimerViewModel extends ViewModel {
private static final String TAG = "TimerViewModel";
private Timer timer;
private int currentSecond;
/**
* 将 currentSecond 这个字段用 MutableLiveData 包装起来
*/
private MutableLiveData liveCurrentSecond;
public MutableLiveData getLiveCurrentSecond() {
if (liveCurrentSecond == null) {
liveCurrentSecond = new MutableLiveData<>();
}
return liveCurrentSecond;
}
/**
* ViewModel最重要的作用是将视图与数据分离,并独立于Activity的重建。
* 为了验证这样一点,在ViewModel中创建一个计时器Timer,每隔1s通过 liveData 通知观察者更UI
*/
public void startTiming() {
if (timer == null) {
currentSecond = 0;
timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
currentSecond ++;
getLiveCurrentSecond().postValue(currentSecond);
}
};
timer.schedule(timerTask, 1000, 1000);
}
}
/**
* ViewModel是一个抽象类,其中只有一个onCleared()方法。
* 当ViewModel不再被需要,即与之相关的Activity都被销毁时,
* 该方法会被系统调用。可以在该方法中执行一些资源释放相关操作
*/
@Override
protected void onCleared() {
super.onCleared();
timer.cancel();
}
}
2.定义完 LiveData 之后,利用它完成页面与ViewModel 间的通信
public class TimerLiveDataActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer_live_data);
final TextView tvTimer = findViewById(R.id.tv_timer);
final Button btnReset = findViewById(R.id.btn_reset_time);
// 通过 ViewModelProvider 得到 ViewModel
TimerLiveDataViewModel timerLiveDataViewModel = new ViewModelProvider(this).get(TimerLiveDataViewModel.class);
// 得到 ViewModel 中的 LiveData
MutableLiveData liveCurrentSecond = timerLiveDataViewModel.getLiveCurrentSecond();
// 通过 LiveData.Observer() 观察 ViewModel 中数据的变化
liveCurrentSecond.observe(this, second -> {
tvTimer.setText(String.valueOf(second));
});
// 重置计时器
btnReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过 LiveData.setValue()/LiveData.postValue()
// 完成对 viewModel 中数据的更新
liveCurrentSecond.setValue(0);
}
});
// 计时开始
timerLiveDataViewModel.startTiming();
}
}
在页面中,通过 LiveData.observe() 方法对 LiveData 所包装的数据进行观察。反过来,当我们希望修改 LiveData 所包装的数据时,也可以通过 LiveData.postValue() / LiveData.setValue()方法来完成。postValue()方法用在非 UI 线程中,若在UI 线程中,则使用 setValue()方法。
LiveData 的原理
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
从源码可以看出,observer()方法接收的第1个参数是一个 LifecycleOwner 对象,在本例中为 Activity。第 2 个参数是一个 Observer 对象。方法中的最后一行代码将 Observer 与 Activity 的生命周期关联在一起。因此,LiveData 能够感知页面的生命周期,它可以检测页面当前的状态是否为激活状态,或者页面是否被销毁。只有在页面处于激活状态(Lifecycle.State.ON_STARTED 或 Lifecycle.State.ON_RESUME)时,页面才收到来自 LiveData 的通知,若页面被销毁(Lifecycle.State.ON_DESTROY),那么 LiveData 会自动清除与页面的关联,从而避免可能引发的内存泄漏问题。
LiveData.observerForever()方法
LiveData 还提供了一个名为 observerForever() 的方法,使用起来与 observer() 没有太大区别。他们的区别主要在于,当 LiveData 所包装的数据发生变化时,无论页面处于什么状态,observerForever() 都能收到通知。因此,在用完之后,一定要记得调用 removeObserver() 方法来停止对 LiveData 的观察,否则 LiveData 会一直处于激活状态,Activity 则永远不会被系统自动回收,这就造成了内存泄漏。
ViewModel + LiveData 实现Fragment间通信
1、通过上面的内容已经知道,ViewModel 能够将数据从 Activity 中剥离出来。只要 Activity 不被销毁, ViewModel 会一直存在,并且独立于 Activity 的配置变化。旋转屏幕所导致的 Activity 重建,也不会影响到 ViewModel。
2、Fragment 可以被看做 Activity 的子页面,即一个 Activity 中可以包含多个 Fragment。这些 Fragment 彼此独立,但是又都属于同一个 Activity。
3、基于 ViewModel 和 Fragment 组件的这些特性,我们可以巧妙地利用 LiveData,实现同一个 Activity 中的 不同 Fragment 间的通信
示例:
1.定义 ViewModel 和 LiveData。使用 LiveData 对 progress 字段进行包装。
public class ShareDataViewModel extends ViewModel {
private MutableLiveData progress;
public MutableLiveData getProgress() {
if (progress == null) {
progress = new MutableLiveData<>();
}
return progress;
}
@Override
protected void onCleared() {
super.onCleared();
progress = null;
}
}
2.为了演示方便,将两个 Fragment 等比例放置在 Activity 的布局文件中。也可以分开放置,然后通过 FragmentManager 进行切换。无论怎样,只要保证这两个 Fragment 属于同一个 Activity 即可。
3.编写 Fragment 的布局文件,在其中放置一个 SeekBar 控件。两个 Fragment 的布局文件是类似的
4.编写 Fragment 的代码,实现具体的通信。这里以 OneFragment 为例, TwoFragment 中的代码与之类似。
public class OneFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_one, container, false);
initView(view);
return view.getRootView();
}
private void initView(View root) {
final SeekBar seekBar = root.findViewById(R.id.seek_bar);
// 注意:这里 ViewModelProvider(getActivity())中的参数
// 需要的是 Activity,而不是 Fragment, 否则将收不到监听
ShareDataViewModel shareDataViewModel = new ViewModelProvider(getActivity()).get(ShareDataViewModel.class);
MutableLiveData liveDataProgress = shareDataViewModel.getProgress();
// 通过 observer 方法观察 ViewModel 中字段数据的变化,并在变化时得到通知
liveDataProgress.observe(this, new Observer() {
@Override
public void onChanged(Integer progress) {
seekBar.setProgress(progress);
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 当用户操作 SeekBar 时,更新 ViewModel 中的数据
liveDataProgress.setValue(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
5.运行程序。
如图所示,无论是滑动 OneFragment 还是 TwoFragment 中的 SeekBar,另一个 Fragment 中的 SeekBar 也会跟着滑动。在滑动 SeekBar 时,通过 LiveData.setValue() 方法,修改了 ViewModel 中 LiveData 包装的数据(progress 字段)。由于 Fragment 通过 LiveData.observer() 方法监听了数据的变化,因此 progress 字段被修改后,Fragment 能够第一时间收到通知并更新 UI。这就是利用 ViewModel 和 LiveData 实现 Fragment 间通信的原理。
旋转屏幕,SeekBar 的进度与旋转前保持一致,数据并未丢失,如图所示。这是因为 ViewModel 的生命周期独立于页面由于配置发生变化而导致的销毁与重建。