学习记录 (5)- LiveData

前言

学习记录系列是通过阅读学习《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()方法。

image.png

LiveData 的原理

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer 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 间通信的原理。

image.png

旋转屏幕,SeekBar 的进度与旋转前保持一致,数据并未丢失,如图所示。这是因为 ViewModel 的生命周期独立于页面由于配置发生变化而导致的销毁与重建。

image.png

总结

1.ViewModel 用于存放页面的数据,当数据发送变化时,我们需要通知页面进行更新。在没有 LiveData 之前,可以通过定义接口完成这个需求,有了 LiveData 后,事情变得更加简单、方便。使用 LiveData 对 ViewModel 中我们关系的数据进行包装,并在页面中对其进行监听,当数据发送变化时,页面就能收到通知,进而更新UI
2.LiveData 的本质时观察者模式,并且它能感知页面的生命周期,只在页面存活时才会进行通知,从而避免了内存泄漏。当前你也可以使用 observerForever()方法让 LiveData 忽略页面的生命周期,但用完后,一定要记得使用 removeObserver() 方法移除监听,否则会造成内存泄漏。
3.LiveData 大部分时候是在 ViewModel 中使用的。但它的作用不止于此,LiveData 在其他地方也能发挥重要作用。Jetpack 在设计之初就充分考虑了组件配合使用的需求。

你可能感兴趣的:(学习记录 (5)- LiveData)