Android Jetpack 组件之 LiveData(Kotlin)

一、简介

LiveData 是 Jetpack 组件之一,使用了观察者模式。当数据发送变化时,通知观察者。

二、API

1.导入 Lifecycle 库

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

2.LiveData 类

LiveData 类是一个抽象类,所以无法直接创建。平时我们主要使用它的两个子类:MutableLiveData、MediatorLiveData。

LiveData 有以下 public 方法:

  • T getValue() 获取 LiveData 的值
  • observe(LifecycleOwner owner, Observer<? super T> observer) 添加观察者,绑定 Lifecycle 生命周期,当 Lifecycle 处于 STARTED 或者 RESUMED 时,认为此观察者是 active 的,否则认为此观察者是 inactive 的。只有处于 active 状态的观察者才会接收消息。
    当 Lifecycle 状态变为 DESTROYED 时,此观察者会被自动移除,所以我们不必担心内存泄漏。
  • observeForever(Observer<? super T> observer) 添加观察者,不绑定生命周期。这样的观察者需要我们调用 removeObserver 手动移除,这样的观察者若没有手动移除,会导致内存泄漏。
  • removeObserver(Observer<? super T> observer) 移除观察者
  • removeObservers(LifecycleOwner owner) 移除绑定此 Lifecycle 的所有观察者
  • boolean hasActiveObservers() 是否有 active 的 observers
  • boolean hasObservers() 是否有 observers,无论 active 还是 inactive

LiveData 有以下 protected 方法:

  • void onActive() 当观察者数量从 0 变成 1 时,回调此方法
  • void onInactive() 当观察者数量从 1 变成 0 时,回调此方法
  • void setValue(T value) 设置 LiveData 的值,不允许在子线程中调用
  • void postValue(T value) post 一个 task 到主线程中,设置 LiveData 的值,所以可以在子线程中调用

如果我们需要这些 protected 方法,可以自定义类继承 LiveData 并重载他们。

3.MutableLiveData 类

MutableLiveData 类是平时使用最多的类,源码如下:

public class MutableLiveData<T> extends LiveData<T> {

    /**
     * Creates a MutableLiveData initialized with the given {@code value}.
     *
     * @param value initial value
     */
    public MutableLiveData(T value) {
        super(value);
    }

    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    public MutableLiveData() {
        super();
    }

    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

观察它的源码我们发现,它相对于 LiveData 的区别就是:它不是抽象类,并且公开了 postValue、setValue 两个方法。使得我们可以调用这两个方法修改它的值。

测试

修改布局文件 activity_main:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="+1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv" />
</androidx.constraintlayout.widget.ConstraintLayout>

修改 MainActivity:

class MainActivity : AppCompatActivity() {
    // 初始化 MutableLiveData
    private val data: MutableLiveData<Int> = MutableLiveData(0)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 开始观察
        data.observe(this, Observer {
            // 当数据改变时,设置到 TextView 上,初始值也会触发这里
            tv.text = it.toString()
        })
        btn.setOnClickListener {
            // 点击按钮时,数据 +1
            data.value = data.value?.plus(1)
        }
    }
}

运行程序,显示如下:
MutableLiveData
如果我们修改一下 MainActivity 中点击事件的代码,点击按钮时,启动一个新线程修改 data 的值:

btn.setOnClickListener {
    Thread(Runnable {
        data.value = data.value?.plus(1)
    }).start()
}

运行时我们会收到以下 Crash:
java.lang.IllegalStateException: Cannot invoke setValue on a background thread

这时,只要将其修改为调用 postValue 方法即可:

btn.setOnClickListener {
    Thread(Runnable {
        data.postValue(data.value?.plus(1))
    }).start()
}

4.MediatorLiveData 类

MediatorLiveData 用来合并多个 LiveData 数据。设想一个场景,我们有两个 LiveData 数据,任何一个数据改变时,都要更新同一个 UI。此时我们就可以用 MediatorLiveData 将其合并。
相比 LiveData,MediatorLiveData 新增了两个公开方法:

  • addSource(LiveData<S> source, Observer<? super S> onChanged) 开始观察此 LiveData,当 source 参数的值改变时,会回调这个 onChanged 函数
  • removeSource(LiveData<S> toRemote) 移除一个 LiveData 类,停止观察此 LiveData

MediatorLiveData 的源码如下:

@SuppressWarnings("WeakerAccess")
public class MediatorLiveData<T> extends MutableLiveData<T> {
    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();

    /**
     * Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called
     * when {@code source} value was changed.
     * <p>
     * {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
     * <p> If the given LiveData is already added as a source but with a different Observer,
     * {@link IllegalArgumentException} will be thrown.
     *
     * @param source    the {@code LiveData} to listen to
     * @param onChanged The observer that will receive the events
     * @param <S>       The type of data hold by {@code source} LiveData
     */
    @MainThread
    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
        Source<S> e = new Source<>(source, onChanged);
        Source<?> existing = mSources.putIfAbsent(source, e);
        if (existing != null && existing.mObserver != onChanged) {
            throw new IllegalArgumentException(
                    "This source was already added with the different observer");
        }
        if (existing != null) {
            return;
        }
        if (hasActiveObservers()) {
            e.plug();
        }
    }

    /**
     * Stops to listen the given {@code LiveData}.
     *
     * @param toRemote {@code LiveData} to stop to listen
     * @param <S>      the type of data hold by {@code source} LiveData
     */
    @MainThread
    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
        Source<?> source = mSources.remove(toRemote);
        if (source != null) {
            source.unplug();
        }
    }

    @CallSuper
    @Override
    protected void onActive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().plug();
        }
    }

    @CallSuper
    @Override
    protected void onInactive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().unplug();
        }
    }

    private static class Source<V> implements Observer<V> {
        final LiveData<V> mLiveData;
        final Observer<? super V> mObserver;
        int mVersion = START_VERSION;

        Source(LiveData<V> liveData, final Observer<? super V> observer) {
            mLiveData = liveData;
            mObserver = observer;
        }

        void plug() {
            mLiveData.observeForever(this);
        }

        void unplug() {
            mLiveData.removeObserver(this);
        }

        @Override
        public void onChanged(@Nullable V v) {
            if (mVersion != mLiveData.getVersion()) {
                mVersion = mLiveData.getVersion();
                mObserver.onChanged(v);
            }
        }
    }
}

查看源码我们可以发现,MediatorLiveData 是通过给传入的 LiveData 新增观察者实现的合并,仍然是使用的 observeForever 和 removeObserver 方法,只是帮我们做了一层简单的封装而已。

测试

修改 activity_main 布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="LiveData1: +1"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="LiveData2: +2"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn1" />
</androidx.constraintlayout.widget.ConstraintLayout>

修改 MainActivity:

class MainActivity : AppCompatActivity() {
    private val liveData1: MutableLiveData<Int> = MutableLiveData(0)
    private val liveData2: MutableLiveData<Int> = MutableLiveData(0)
    private val liveDataMerger: MediatorLiveData<Int> = MediatorLiveData<Int>().apply {
        this.value = 0
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        liveDataMerger.addSource(liveData1) {
            liveDataMerger.value = it
            // 红色表示此时为 LiveData1
            tv.setTextColor(Color.RED)
        }
        liveDataMerger.addSource(liveData2) {
            liveDataMerger.value = it
            // 绿字表示此时为 LiveData2
            tv.setTextColor(Color.GREEN)
        }
        liveDataMerger.observe(this, Observer {
            tv.text = it.toString()
        })
        btn1.setOnClickListener {
            liveData1.value = liveData1.value?.plus(1)
        }
        btn2.setOnClickListener {
            liveData2.value = liveData2.value?.plus(2)
        }
    }
}

运行程序,显示如下:
MediatorLiveData

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值