重新学习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)
}
}
上面的代码在Fragment
的onCreate
中向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总体生命周期上,也就是它本身的onCreate
和onDestroy
这些方法,而viewLifecycleOwner
绑定到UI相关的生命周期上,也就是onCreateView
和onDestroyView
上。
如果使用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值更改。这种特性叫做“粘性事件”,上一篇笔记中我们自己编写出的代码也具有这种特性。在某些业务中,我们需要这种特性,在某些业务中,我们不需要并且完全不能容忍这种情况出现。后面会说如何解决这个问题。
dispatchingValue
是LiveData
类的方法了,所以你也能看到,这里将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());
// ...
}
}
// ...
}
setValue
和postValue
方法里也都是调用了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中的值更改了。
所以,总结一下:
- LiveData可以添加观察者
- LiveData添加观察者时具有粘性事件特性,如果添加之前LiveData中已经有值,那么该观察者一旦处于活跃状态时便会立即接收到最新的值
- LiveData并没有提供关闭粘性事件的API,不过我们仿佛可以通过一些手段(比如修改值版本)来关闭粘性事件特性
- 使用
observe
方法时,LiveData会观察它的观察者的生命周期,只有当观察者生命周期处于活跃时才会接收到LiveData值的更新,当观察者处于销毁状态时,该观察者会被移除 - 当观察者状态由其他状态转为活跃时(比如旋转屏幕),如果观察者中的值比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
是不可变的,它不可以setValue
、postValue
。因为可变类型是不可变类型的子类,所以通过私有化可变类型,并且公开一个不可变类型,该不可变类型其实就是可变类型的向下转型,但没办法,因为类型已经向下转为不可变的了,所以外界无法调用setValue
和postValue
,同时在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套件的学习阶段,还没有实际写过项目,所以也没什么机会遇到问题,不知道粘性事件到底会带来怎样的困扰,所以我这里先不写了,但我先放上其他大佬的解决办法
参考#
作者:Yudoge
出处:https://www.cnblogs.com/lilpig/p/15759257.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
欢迎按协议规定转载,方便的话,发个站内信给我嗷~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)