导言

关于LiveData,在2022尾声的今日,从事 Android 开发的小伙伴必定不会生疏。相应的,关于 LiveData 解析与运用的文章更是数不胜数,其间不乏优异的创造者,在众多的文章以及前辈面前,本篇也不敢妄谈能写的多么深入,易懂。

本篇首要想着重聊聊 LiveData 的完成思维,以及与之相关联的一些问题,试着从另一角度告知你这些答案,或许说是个人的一些了解。

在阅读本文前,主张读者有以下前置常识储备:

  • 熟悉并会运用 LiveData
  • 了解 Lifecycle 的规划;

导航

学完本文,你将了解以下内容:

  • LiveData 简要快析;
  • LiveData 源码简析;
  • LiveData 规划思维;
  • LiveDataLifecycle 的关联;
  • LiveData 用作事情告知时的危险;
  • LiveDataEventBus 的差异是什么;
  • LiveDataFlow 我该怎样选;

好了,让咱们开始吧!

LiveData简要快析

在官方的描绘中,LiveData 如下所示:

LiveData 是一种可调查的数据存储器类。与惯例的可调查类不同,LiveData 具有生命周期感知才能,意指它遵从其他应用组件(如 activityfragmentservice)的生命周期。这种感知才能可保证 LiveData 仅更新处于活泼生命周期状况的应用组件调查者。

说简略便是 LiveData 是一个可调查的数据存储类,内部凭借了 Lifecycle,然后完成了生命周期感知,一起,这个可调查指的是,在其存储的数据更新时,它会去告知调查者。又由于生命周期感知的存在,所以能够做到 何时告知、何时解绑,然后做到安全无泄漏,便是如此:)

LiveData与Lifecycle的关联

说一句比较夸张的话,没有 Lifecycle,天然也不会存在 LiveData,或许说应该改名为 ObserveDataLiveData 作为作为生命感知型组件一部分,自诞生之初其,就离不开 Lifecycle 这个基石。

LiveData 规则了,当咱们开发者订阅数据告知(调用observe())时,有必要传递相应的 lifecycle 目标,其内部天然便是为了注册相应的调查者,然后做到生命周期感知,不然它怎样能自己解绑呢?

当咱们的调查者生命周期处于 STARTD 或许 RESUMED 状况,LiveData 就会以为当时调查者处于活泼状况,此刻就会触发相应的更新告知,而非活泼的调查者天然不会收到告知。也正是由于 Lifecycle 的原因,所以 LiveData 做到了主动解绑,然后防止内存泄漏。

关于 Lifecycle ,这儿也趁便再提一下:

说到Lifecycle,在sdk26以后,Lifecycle现已被写入了咱们 Androidx 根底组件,默认会在 ComponentActivityFragmeent 中初始化,并且支撑开发者自行调用 lifecycle 目标,然后添加相应的生命周期调查者,然后革除模版代码。相应的,Lifecycle 将生命周期划分为了如下几个阶段:

  • DESTROYED
  • INITIALIZED
  • CREATED
  • STARTED
  • RESUMED

这几个阶段与咱们开发者其实并不相关,开发者往往重视的是其对应的Event。即 Lifecycle 将生命周期划分为多个状况,当生命周期改变时,就会触发生命周期事情告知(比方 onResume() 等),然后同步当时的状况,而状况相当于一个事情集,其代表了当时 lifecycle 的状况,然后不拘泥于现在 Event 究竟处于什么。

LiveData规划思维

其实,要了解 LiveData 的规划思维,最简略的办法便是手动完成一遍,所以本末节将完整叙说一遍 LiveData 的整体规划流程。‍

在开始之前,咱们先看一段普通的示例代码,如下所示:

private val _livedata: MutableLiveData<String> = MutableLiveData()
val liveData: LiveData<String> = _livedata
fun manager(){
  _livedata.postValue(x)
  _livedata.setValue(x)
}
fun observeX(){
  liveData.observe(lifecycle,Observer)
}

LiveData 的运用一般如上所示,咱们一般会先初始化一个 MutableLiveData 目标,然后对外暴漏 LiveData 目标,然后遵从开闭准则,外部调用者只允许订阅调查者,调查数据更新,而不允许主动告知数据更新,当然这也是 LiveData 的规范引荐用法。


假如咱们自己要完成一个 LiveData ,其内部保护着一个数据,并且要保证这个数据在更新时,调查者能够收到告知,并且要在页面活泼状况才行。此刻,就有如下几个问题:

  • 数据怎样保护?
  • 数据什么时候告知? 告知时机呢?

而要说清上述问题,即正是对LiveData的规划思维做一个论述。

  1. 要满意上述条件,咱们需求规划一个类,假定名字叫做 ObserveData,并且内部持有一个数据T,由于要支撑多种数据类型,所以泛型也必不可少;
  2. 为了支撑数据监听,咱们需求新增一个详细的监听数据更新办法,假定名字叫做 observe() ,当然也需求传入详细的调查者 IObserve 接口目标;
  3. 为了支撑数据更改,咱们需求新增一个详细的设置数据的办法,假定名字叫做 setValue();
  4. 为了在用户调用 setValue() 更新数据时,告知用户变更,咱们需求新增一个调查者列表map,然后将用户 observe() 传递进来的调查者保存起来;
  5. 为了契合Android的生命周期,保证页面活泼状况才能收到告知,然后防止非活泼调查者被告知到,节省性能;以及能不能将解绑逻辑让框架自行履行,然后革除调用者手动调用模版代码;天然而然,咱们就会想到 Lifecycle ,所以咱们能够在 observe() 这儿做改动:
    • 咱们更改了 observe() 办法,调用者有必要传递 lifecycle 目标进来;
    • 咱们新增了一个新的包装类假定名字叫做 ObserverLifecycleWrapper ,其需求完成 LifecycleEvent 接口,以及内部保存着咱们的调查者;
    • 最后,当用户在调用 observe() 订阅数据更新时,咱们就将用户传递的调查者运用包装类包装起来,并缓存到咱们的调查者map中,接着再将其 add()lifecycle 的生命周期调查数组里,然后便于收到生命周期更新告知;
  6. 上述的完成看似简略,但细心考虑就有个问题,假如调查者此刻处于不活泼状况呢?此刻用户更改了数据,那这个数据更改就没法告知给用户;那假如调查者又转为活泼状况了,本次更改岂不是越过了?相应的,咱们又怎样保证同一个数据更新不会触达用户两次呢?
    • 为了处理上述问题,咱们添加了 [版别号] 的概念,咱们的 ObserveData 中持有一个最新版别号,每一个调查者包装类 ObserverLifecycleWrapper 也保护着这个的版别号。即当用户每次手动更新数据时,咱们对 版别号进行++ ,然后再去告知相应的调查者,假如这个 调查者的版别号<小于当时ObserveData最新的版别号,咱们就以为这个调查者仍然持有着旧的数据,就对其进行更新,并将新的版别号赋值给这个调查者;
    • 相应的,由于咱们的调查者订阅了 lifecycle 生命周期更新,所以当生命周期由非活泼转为活泼状况时,咱们就再去对比一下当时调查者的最新数据版别号与咱们当时最新的版别号是否一致,假如不一致,则主动更新;否则越过。

上述思路看着很繁琐,但其实比较简略,也即是 LiveData 的整个规划思路,但假如你了解 Lifecycle ,上述的了解我想对你来说,便是 so easy。

但细心调查,不难发现上述的思路中,好像隐藏着一些问题,而这些问题,好像也是充满一些争议,比方每次 observe() 时,由于lifecycle + version 的问题,会导致新的调查者从头订阅后数据被回推,而关于这个问题咱们也会在后面进行弥补。

LiveData源码简析

在上面咱们论述了 LiveData 的规划思维,有了上面的根底,那么再看源码就十分简略了。

而要探求 LiveData 的源码,咱们只需求去看看相应的 observe()postValue() 即可,为什么这么说呢?

原因很简略,一个好的框架库,会遵从 开闭与最少准则,即暴漏给开发者往往只要几个首要办法。而在 LiveData 的规划中,observe()postValue() 两个办法是离咱们开发者最接近的,而了解完这两个办法,也就不难了解LiveData的底层完成,以及为其他问题解析做出衬托。

虽然也有 observeForever()removeObserve(),但这些都比较简略,不影响咱们阅读主流程。

observe()

用于订阅LiveData的数据更新,源码如下:

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
  	// 创立生命周期绑定调查者,这儿相当于是对咱们调查者的一个包装
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer)
    // 将调查者添加到缓存中,假如存在,则越过
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper)
    ...
    // 将调查者添加到Lifecycle订阅列表中,即赋予生命周期订阅
    owner.getLifecycle().addObserver(wrapper)
}

在调用 observe() 订阅 Livedata 数据更新时,这儿相当于添加了一个调查者,办法内部会将咱们传递的 LifecycleOwner 与 调查者 包装为一个详细的生命周期调查者 wrapper(LifecycleEventObserver),接着将这个 wrapper 添加到当时的调查者列表中,假如存在则停止本次订阅操作,否则将这个调查者添加到 lifecycle 生命周期订阅列表。

由于 LifecycleEventObserver 完成了 LifecycleEventObserver 接口,故这个 wrapper 实则具备了生命感知,所以不难猜测,LiveData 为什么能做到主动解绑,页面活泼时接收消息,也是由于 lifecycle 的原因。


postValue()

用于在非主线程更新 LiveData 中持有的数据,内部最终会调用 setValue() ,详细如下:

protected void postValue(T value) {
    boolean postTask;
  	// 进入目标锁
    synchronized (mDataLock) {
      	// 数据是否set过
        postTask = mPendingData == NOT_SET;
      	// 待同步的数据
        mPendingData = value;
    }
  	// 当时正在postValue,忽略本次操作
    if (!postTask) {
        return;
    }
  	// 将使命发送到主线程履行
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
 private final Runnable mPostValueRunnable = new Runnable() {
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
              	// 获取最新待设置的数据
                newValue = mPendingData;
              	// 重置待同步的数据为默认
                mPendingData = NOT_SET;
            }
          	// 设置数据
            setValue((T) newValue);
        }
    };

上述办法的完成很巧妙,内部会先判别当时是否正在更新数据(即数据是否为默认),然后将咱们要设置的数据保存起来,假如正在更新,则越过本次使命发送,否则将本次更新使命发送到主线程去履行(不难猜测内部也是handler履行),在详细的 runable 中,会直接去取最新待同步的值,然后将其置为默认值,最后履行实在的数据更新,即 setValue();

不过需求留意的,多线程下调用,或许会丢掉某次的告知。


setValue()

用于在主线程更新 LiveData 持有的数据,其内部实则分为了三个过程,如下所示:

-1. 设置数据:

protected void setValue(T value) {
  	// 版别号++
        mVersion++;
  	// 同步数据
        mData = value;
  	// 分发数据
        dispatchingValue(null);
}

-2. 分发数据:

void dispatchingValue(@Nullable ObserverWrapper initiator) {
     ...
     // 不为null时证明是lifecycle状况变为活泼
     if (initiator != null) {
         considerNotify(initiator);
         initiator = null;
     } else {
       	//setValue时触发, 轮训调查者列表去更新
         for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                 mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
             considerNotify(iterator.next().getValue());
           	 // 假如分发失效,直接跳出(非要害点)
             if (mDispatchInvalidated) {
                 break;
             }
         }
     }
  	 ...
}

-3. 告知调查者:

private void considerNotify(ObserverWrapper observer) {
  	// check当时调查者持有的生命周期状况,即非onsStart-onPause时直接return
    if (!observer.mActive) return;
 	  // 再次check调查者最新状况,即检查lifecycle对应的状况
    if (!observer.shouldBeActive()) {
      	// 假如非活泼状况,告知调查者当时非活泼状况
        observer.activeStateChanged(false);
        return;
    }
  	// 版别检测,假如当时调查者持有的版别>=当时的版别,即证明现已更新过了
    if (observer.mLastVersion >= mVersion) {
        return;
    }
  	// 更新调查者当时的版别
    observer.mLastVersion = mVersion;
  	// 履行数据告知
    observer.mObserver.onChanged((T) mData);
}

让咱们总结一下上述的整体思路,当咱们调用 setValue() 时,内部会对当时 LiveData 持有的版别号 version 进行自增,然后调用dispatchingValue() 去分发本次数据,然后会去轮训当时的调查者列表,然后判别调查者是否是活泼状况,即是否是 onStrat() – onPause() 之间,假如是并且当时调查者的版别号小于 LiveData 保护的版别号,由此证明当时调查者尚未告知过,然后触发告知。

LiveData用作事情告知时的危险

关于这个问题,经常会被开发者提起,或许叫做数据倒灌,数据回推更为适宜,但这些问题其实都是在 [不正确] 的布景下运用LiveData导致。

比方常见于同享的 LiveData ,运用 LiveData 作为事情告知,咱们会发现为什么刚刚 observe() 的调查者,马上就呼应了数据更新,并且还是旧数据,那这是为什么呢?

问题很简略,在上面咱们现已说过了,当咱们调用 observe() 添加数据调查者时,内部实践会被包装为 LifecycleBoundObserver,然后添加到 lifecycle 的生命周期调查者列表。而熟悉 lifecycle 的小伙伴,必定了解了,当咱们添加 lifecycle 生命周期调查者时,其调查者的生命周期状况会被相应的履行到当时的 lifecycle 状况,所以天然会调用 LifecycleBoundObserver 中的状况更新办法,然后触发了数据分发。而又由于这个调查者是新添加进去的,调查者持有的数据版别号是默认的,即-1,可是 LiveData 内部的数据版别号可不是啊!,所以天然触发了数据更新告知。


那这个问题归于LiveData的规划问题吗?

并不归于,相反这个规划,是十分契合生命周期组件的界说。

LiveData 往往是为了界面数据的状况同步而作准备,所以当添加调查者后,被再次告知,也不难了解。由于关于页面而言,这个调查者的确是新添加的,假如 LiveData 中存在数据,必定需求第一时间同步到页面更新。

详细咱们看一眼官方对其的描绘:

由浅入深,详解 LiveData 的那些事


但既然 LiveData 这么安全好用,所以就会有开发者想着运用 LiveData 用于事情告知,此刻它的规划在某种程度上就成了问题,虽然在官方的主张里,十分不主张直接这么用。

常见有如下几个处理思路:

  • 反射处理version

    在调用 observe() 办法里,反射相应的包装类 ObserverWrapper ,把其的版别号更改为 LiveData 现有的版别号;

  • SingleLiveEvent

    计算机科学范畴一直流传着一句格言:任何问题都能够经过添加一个间接的中间层来处理。

    咱们手动保护一个符号,并在 observe() 办法里,并再次包装调查者 Observer,这样当数据每次告知时,咱们就能够拦截,然后用这个符号做判别,假如契合要求,则调用实在调查者的告知办法,并更新符号值。在咱们每次 setValue() 时,再重置这个符号即可。

    详细可拜见 Android-architecture-simple-SingleLiveEvent

  • 手动保护version

    这个办法能够说是对 SingleLiveEvent 的一个完善与弥补。

    既然 version 咱们无法防止,那么不如咱们自己保护一个 version ,即承继 LiveData ,自己保护 version ,一起添加一个新的调查者包装类,内部持有一个版别号,对传递进来的调查者进行包装,并重写相应的 onChanged() 办法,内部会去判别调查者当时版别号,假如当时版持有的版别号<咱们自己保护,则触发更新,并且更新调查者版别号;当咱们每次 setValue() 时,并对 version 进行自增;在 observe() 时,再将当时持有的 version 赋值给咱们的包装类,然后完成了整个套娃流程。

    详细可拜见 KunMinx的UnPeek-LiveData-ProtectedUnPeekLiveData

  • 改用其他办法

    处理不了问题,就把提出问题的处理了:)

    人生苦短,我选 Flow(SharedFlow)。

LiveData与EventBus怎样选

先说定论,这两者并不抵触,首要由于其各自负责的事情不一样。

  • LiveData 用于处理[界面]的数据的状况,即常用于界面的数据状况同步;
  • EventBus 是用于事情总线,即是分发App中所有事情的一个中转站;

前者常用于于处理界面数据状况,并且遵从 Android 生命周期模式。而后者是作用于事情告知,即能够保证本次宣布的事情必定会被可调查的接收者收到,虽然后者也支撑 Sticky ,这点好像和LiveData类似,但这两者在思维上本来便是大不相同。

关于开发者而言,由于两者运用起来的共性何其的类似,特别是作用于同享的页面时,开发者很简略会想到二选一问题,但事实上,细心分析的话,就会发现:

  • 关于LiveData,这归于同享页面的数据状况同步;
  • 关于EventBus而言,这归于同享页面[事情]的告知;

两者完全不在一个范畴,即EvenBus不会关怀你的数据后续,它只关怀事情告知了吗? 我要不要在你订阅时再告知你这个事情?而LiveData会帮你持有这个数据状况,一起需求关怀我有必要在适宜的生命周期内再告知你,以及在你从头订阅时再次告知你(假如存在数据)。

由于LiveData其自身的规划驱使,由此也很简略诞生LiveDataBus,在详细的功能上,其做的事情和 EventBus 类似,在某些特性上,乃至优于后者。详细能够拜见美团的 LiveEventBus

LiveData和Flow怎样选

这儿的 Flow 通常其实指 StateFlowSharedFlow

这个问题,也常被开发者提起。诸如,官方引荐在 MVVMMVI 中运用 Flow ,便是要革了 LiveData 的命?但其实,这两者也没什么直接抵触。

搞点小彩头,关于 非Kotlin 项目,你怎样用 Flow ?

Flow: 那我走?

Rx: 我来我来。

先说说 Flow ,其指的是 Kotlin 中的数据流,虽然功能上不如Rx强壮,但在 Kotlin 的布景下,其无疑是最佳伙伴,究竟有协程这个好兄弟在,因此,Android团队主张运用 Flow 替换 LiveData

再说说 LiveData ,其本规划简略轻盈,但功能不强,仅仅只能用于数据状况的同步。在多线程下 postValue ,乃至会丢掉某次的数据更改(其自身也不引荐用于告知事情的作用),不过也没什么问题,由于其自身就不是用来帮你做频频数据处理的。

说的更详细点:

在2017年,Kotlin 的占有率可没那么高,所以 LiveData 作为 AAC 的重要组件天然承当了大部分责任。而在2022的今日,Kotlin 在Android开发中的占有率早现已超越63%(这仅仅2021年计算),跟着日益添加的事务与架构应战,LiveData 显然不能满意更多需求,架构也需求更先进的组件支撑。

相比 LiveData ,Flow 就显得愈加强壮,不只独立于详细的视图层,并且其能够单独的集成到事务模块。在功能上,支撑数据的各种处理,搭配协程,是 Kotlin 布景下不可获取的利刃。相应的,在 Android上 面,Flow 也能够经过 asLiveData() 然后转为LiveData,由此兼容运用。

假如你的项目是 Java 编写,那 LiveData 仍然是你保护页面数据状况的最好伙伴。

假如你的项目是 Kotlin 编写,那么 LiveData 仍然能够满意你的需求。但假如你想做更多事,比方想在发送数据时趁便处理一下,然后更主动的完成数据状况的处理,Flow 或许愈加契合你的要求,当然你也能够随时将 Flow 转为传统的 LiveData 运用(对外部调用者而言)。当然需求留意的是,Flow 并不能感知 Android 的生命周期,你或许需求再添加一些模版代码,但好在Android团队做了各种扩展办法,这个成本在今日也是十分小。

总结

本篇,咱们经过问题解答的办法,由浅入深,回顾了 LiveData 的规划思维,以及其相关的一些疑问,然后从本源上解说了这些问题。

到了这儿,咱们也无需再拘泥于随声附和的数据是否倒灌、规划是否合理、究竟和其他组件该怎样选、项目中究竟该用什么,这些问题我相信都不是问题。由于在不谈的布景的情况下,没有肯定的规范与统一的准则,那就更别提对与错。但至少关于 LiveData 而言,了解完本篇的你,我相信再也不会再有相关疑问。

参看

  • 官方文档-LiveData概述
  • 如何优雅的运用LiveData完成一套EventBus(事情总线)
  • [Android]/architecture-samples/SingleLiveEvent
  • [KunMinx]/UnPeek-LiveData/ProtectedUnPeekLiveData

关于我

我是 Petterp ,一个 Android工程师 ,假如本文对你有所协助,欢迎点赞支撑,你的支撑是我继续创造的最大鼓励!