Loading

重新学习Android——(五)LiveData概念、用法以及源码分析

可观察的LiveData

上一篇笔记中,我们在Fragment中编写了这样的代码:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // ...

    val viewModel = ViewModelProvider(requireActivity()).get(UserViewModel::class.java)

    viewModel.addOnUserSelectedListener {
        username.text = it.name
        ageAndSex.text = "${it.age}岁 ${it.sex}"
        desc.text = it.description
    }

}

它观察ViewModel中用户选中状态的改变,一旦发生改变,就调整UI。

同时,我们必须在ViewModel中添加相应的代码来暴露让外部组件监听变化的API:

class UserViewModel : ViewModel() {

    // ... 

    private val onUserSelectedListeners: MutableSet<(User) -> Unit> = mutableSetOf()

    fun addOnUserSelectedListener(listener: (User) -> Unit) {
        onUserSelectedListeners.add(listener)
        // 当该listener添加之前已经有用户被选中了,先回调一下
        selectedUser?.let {
            listener(it)
        }
    }

    fun removeOnUserSelectedListener(listener: (User) -> Unit) {

      onUserSelectedListeners.remove(listener)
    }
}

上面的代码在FragmentonCreate中向ViewModel添加了监听器,用观察者模式的话来说就是订阅了一个观察者。这个操作很危险,它把原来在Fragment中的观察者回调函数的引用交给了ViewModel,而ViewModel的生命周期如果长于这个Fragment(一般情况下都是的),这时就会发生内存泄漏。因为Fragment已经销毁,但这个回调还被ViewModel所引用,无法销毁,并且这时当用户再次发生改变时,ViewModel还会通知这个监听器,这样可能发生程序崩溃。

所以我们最后还添加了一个生命周期感知型组件,让它来观察Fragment的生命周期,在适当的时候移除用户选择的监听。

class UserSelectedListener(
    private val viewModel: UserViewModel,
    private val userSeletedListener: (User) -> Unit
) : DefaultLifecycleObserver{
    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        viewModel.addOnUserSelectedListeners(userSeletedListener)
    }

    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)
        viewModel.removeOnUserSelectedListeners(userSeletedListener)
    }

}

But!需要这么麻烦??我们才写了一个小小的功能啊,就要写这么多代码??ViewModel维护了所有用户改变监听器的列表,ViewModel提供添加和移除监听器的API,ViewModel负责在选中用户时通知所有监听器。。。同时我们还得为了安全来维护和组件生命周期相关的代码。。。

这时LiveData就出现了,它是一个可观察对象,Fragment等组件可以向其添加观察者,添加观察者时,LiveData会感知组件的生命周期,当组件销毁(DESTROYED)时,LiveData便会自动退订这个观察者,当组件处于非活跃状态时(非STARTED和RESUMED),LiveData也不会向其发送通知。

所以,我们只需要在ViewModel中暴露一个LiveData对象,然后再让Fragment去观察它,即可完成我们上篇笔记中写了很久的逻辑,来修改一下。

class UserViewModel : ViewModel() {
    val users = arrayListOf(/*...userlist...*/)

    val userSelected: MutableLiveData<User> = MutableLiveData()

    fun select(user: User) {
        userSelected.value = user
    }
}

然后修改Fragment中的代码

class UserDetailsFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // ...bind view...

        val viewModel = ViewModelProvider(requireActivity()).get(UserViewModel::class.java)

        viewModel.userSelected.observe(viewLifecycleOwner) {
            username.text = it.name
            ageAndSex.text = "${it.age}岁 ${it.sex}"
            desc.text = it.description
        }

    }
}

上面的所有功能就完成了,我们来解释一下这些代码。

首先ViewModel中通过MutableLiveData<User>创建了一个可变的LiveData对象,并且泛型指定了这个LiveData对象用于存储User。

然后在Fragment中,通过观察viewModel中的这个LiveData对象来进行选中用户更改时UI更新

viewModel.userSelected.observe(viewLifecycleOwner) {
  // update ui...
}

observe方法接收两个参数,第一个参数是LifecycleOwner对象,第二个参数是LiveData中数据更新时的回调。LiveData会观察这个LifecycleOwner的生命周期,在它活跃时才回调它,并且在他销毁时丢弃它的回调,不再向它发送更新。

还有一个问题,viewLifecycleOwner是啥?Fragment也是一个LifecycleOwner,为什么这里不使用this

因为在Fragment中,this绑定到Fragment总体生命周期上,也就是它本身的onCreateonDestroy这些方法,而viewLifecycleOwner绑定到UI相关的生命周期上,也就是onCreateViewonDestroyView上。

如果使用this,而非viewLifecycleOwner的话,视图销毁了但Fragment本身还没有销毁时,LiveData会继续向观察者发送改变事件,这可能会引起程序崩溃。

Google也推荐我们使用viewLifecycleOwner

查看LiveData源码

我们可以通过查看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);
  }

我们只看observe方法,首先它判断了observe必须是在主线程被调用的,然后检测了一下当前LifecycleOwner是否已经销毁,已经销毁就直接退出方法了。

然后使用传入的LifecycleOwner和LiveData数据更改回调创建了一个LifecycleBoundObserver,后面所有的数据更新时回调,对LifecycleOwner生命周期的监听都托管给这个对象处理。

然后就是判断了以下是否以不同的生命周期添加了相同的观察者,如果是就抛出异常。然后如果传入的生命周期是第一次观察该LiveData,那么向那个生命周期中添加观察者,添加的观察者就是刚刚的LifecycleBoundObserver

现在我们的视线就完全转入了LifecycleBoundObserver中。

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver

看它类的签名,我们能知道,它首先是一个LiveData的ObserverWrapper,它的一个作用是包装其他组件对LiveData传入的观察者。其次,它还是一个LifecycleEventObserver,它会检测观察者的生命周期,并在特定的生命周期时做特定的事。

@Override
public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();

    // 如果当前状态是销毁,那么移除LiveData观察者
    if (currentState == DESTROYED) {
        removeObserver(mObserver);
        return;
    }

    // 下面的循环用于更新当前的状态是否是活跃状态
    // 并且在其他状态转入活跃状态时,调用LiveData监听者的回调
    Lifecycle.State prevState = null;
    while (prevState != currentState) {
        prevState = currentState;
        activeStateChanged(shouldBeActive());
        currentState = mOwner.getLifecycle().getCurrentState();
    }
}

@Override
boolean shouldBeActive() {
    return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}

看下activeStateChanged方法中的内容,它是ObserverWrapper类的方法

void activeStateChanged(boolean newActive) {
    if (newActive == mActive) {
        return;
    }
    // immediately set active state, so we'd never dispatch anything to inactive
    // owner
    mActive = newActive;
    changeActiveCounter(mActive ? 1 : -1);
    if (mActive) {
        dispatchingValue(this);
    }
}

它会在当前转入的状态是活跃状态时调用dispatchingValue来给LiveData观察者分派值(就是调用观察者的回调)。所以,如果LiveData中有值时,尽管是在当前观察者开始观察之前被设置的,当前观察者也能够立即观察到该值,而不用等到下次LiveData值更改。这种特性叫做“粘性事件”,上一篇笔记中我们自己编写出的代码也具有这种特性。在某些业务中,我们需要这种特性,在某些业务中,我们不需要并且完全不能容忍这种情况出现。后面会说如何解决这个问题。

dispatchingValueLiveData类的方法了,所以你也能看到,这里将this传入,也就是将ObserverWrapper传入,这样dispatchingValue方法才知道将值分配到哪一个观察者身上。如果dispatchingValue参数中的ObserverWrapper为null,那么它会向所有注册的观察者分配值。相关代码如下

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    // ...

    // 如果observerWrapper不为null,那么只考虑通知指定的观察者
    if (initiator != null) {
        considerNotify(initiator);
        initiator = null;
    } else {
        // 否则,通知所有观察者
        for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
            considerNotify(iterator.next().getValue());
            // ... 
        }
    }
    // ...
}

setValuepostValue方法里也都是调用了dispatchingValue方法来通知observer更新值,不同的是,这两个方法就没有传入ObserverWrapper,也就是说,它们会向所有观察者分派值。

dispatchingValue中又会调用considerNotify来视情况考虑是否通知观察者LiveData值是否已经改变。

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

这里就是再次判断当前观察者的生命周期是否在活跃状态,并且检测当前观察者的值的版本和当前LiveData中值的版本,只有当观察者生命周期活跃并且观察者中的版本小于当前LiveData中值的版本时才会调用观察者的回调,通知观察者LiveData中的值更改了。

所以,总结一下:

  1. LiveData可以添加观察者
  2. LiveData添加观察者时具有粘性事件特性,如果添加之前LiveData中已经有值,那么该观察者一旦处于活跃状态时便会立即接收到最新的值
  3. LiveData并没有提供关闭粘性事件的API,不过我们仿佛可以通过一些手段(比如修改值版本)来关闭粘性事件特性
  4. 使用observe方法时,LiveData会观察它的观察者的生命周期,只有当观察者生命周期处于活跃时才会接收到LiveData值的更新,当观察者处于销毁状态时,该观察者会被移除
  5. 当观察者状态由其他状态转为活跃时(比如旋转屏幕),如果观察者中的值比LiveData中的最新值更旧(版本小于LiveData中的版本),观察者会立即接收到LiveData最新值,该条其实已经包含了第三条。

LiveData和MutableLiveData

MutableLiveData意为“可变的LiveData”,它允许在主线程中通过setValue(或kotlin中的value属性)对其值进行更改,允许在其他线程中通过postValue对值进行更改,但无论如何,观察者的回调都会在主线程中被调用,postValue完成了子线程到主线程的转换,具体可以查看其代码,这里就不分析了。

那如果我们在ViewModel中直接暴露MutableLiveData,那外部就随便更改值了,要知道,根据分离关注点原则,Fragment、Activity这些地方不应该直接改变UI状态,它们只是负责接收并上报用户事件,接收UI状态的更新,渲染界面。

所以我们应该改改之前ViewModel中的代码:

private val _userSelected: MutableLiveData<User> = MutableLiveData()
val userSelected: LiveData<User>
get() = _userSelected

fun select(user: User) {
_userSelected.value = user
}

这是一个常见的手法,LiveData是不可变的,它不可以setValuepostValue。因为可变类型是不可变类型的子类,所以通过私有化可变类型,并且公开一个不可变类型,该不可变类型其实就是可变类型的向下转型,但没办法,因为类型已经向下转为不可变的了,所以外界无法调用setValuepostValue,同时在ViewModel类内部还保留着更改原始可变类型的值的权力。

扩展LiveData

可以通过继承LiveData类来编写个性化的LiveData类。

下面是官方示例中一个用于持续监听股票价格的LiveData:

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    // 初始化股票管理器
    private val stockManager = StockManager(symbol)

    // 股票管理器的监听器,一旦股票价格发生改变,就更新LiveData的Value
    // 现在的模式是:股票价格监听器 -> LiveData -> LiveData观察者
    private val listener = { price: BigDecimal ->
        value = price
    }

    // onActive是当LiveData开始具有活跃观察者时会被回调的方法
    override fun onActive() {
        // 开始监听股票价格更新
        stockManager.requestPriceUpdates(listener)
    }

    // onInactive时当LiveData没有任何活跃观察者时被回调的方法
    override fun onInactive() {
        // 不再监听股票价格更新
        stockManager.removeUpdates(listener)
    }
}

当从没有观察者转到有观察者时,onActive会被调用,当从有观察者转到无观察者时onInactive会被调用。

您可以在多个Activity和Fragment之间共享同一LiveData对象,这没什么大不了的,它可以不依赖任何组件的生命周期而存在,同时它又可以感知每一个观察者的生命周期,保证不会发生崩溃和内存泄漏。

比如通过如下代码将它实现为一个单例:

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager: StockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }

    companion object {
        private lateinit var sInstance: StockLiveData

        @MainThread
        fun get(symbol: String): StockLiveData {
            sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
            return sInstance
        }
    }
}

MediatorLiveData

LiveData的子类,它可以合并多个LiveData源,当原始LiveData源中任意LiveData值发生改变,MediatorLiveData的观察者就会收到更新。

转换LiveData

如下代码使用Trasformations.map函数,它将userLiveData转换成了另一个LiveData<String>。当userLiveData的值发生改变时,这个userName也会发生改变。有点像Vue里的computed

val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
    user -> "${user.name} ${user.lastName}"
}

这么高科技的功能如何实现的?其实map方法里只是用了MediatorLiveData,并且只给它添加一个源,这样就做到了当源LiveData更新,这个MediatorLiveData也更新。

@MainThread
public static <X, Y> LiveData<Y> map(
        @NonNull LiveData<X> source,
        @NonNull final Function<X, Y> mapFunction) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        @Override
        public void onChanged(@Nullable X x) {
            result.setValue(mapFunction.apply(x));
        }
    });
    return result;
}

所以,这并不是魔术,当源对象中,map并未依赖的属性发生改变时,比如user.age,新对象的观察者也会收到通知,尽管新LiveData的值并未发生实质性的改变。

还有另一个函数是Transformations.switchMap,它们差不多,只不过map的最后一个参数是直接返回一个值的函数,switchMap的最后一个参数要求是返回一个LiveData的函数。

你也可以使用MediatorLiveData自定义其他种类的转换。

LiveData粘性事件

第一个想到的办法就是通过干涉LiveData中的版本来做到,但是这样就需要用到反射。

同时我还处在Jetpack套件的学习阶段,还没有实际写过项目,所以也没什么机会遇到问题,不知道粘性事件到底会带来怎样的困扰,所以我这里先不写了,但我先放上其他大佬的解决办法

UnPeek-LiveData - Github

参考

posted @ 2022-01-03 12:27  yudoge  阅读(313)  评论(0编辑  收藏  举报