写点什么

响应式编程在 Android 中的一些探索,android 三种开发模式

用户头像
Android架构
关注
发布于: 刚刚

二.LiveData

LiveData 是 google 发布的 lifecycle-aware components 中的一个组件,除了能实现数据和 View 的绑定响应之外,它最大的特点就是具备生命周期感知功能,这使得他具备以下几个优点:


  • 解决内存泄漏问题。由于 LiveData 会在 Activity/Fragment 等具有生命周期的 lifecycleOwner onDestory 的时候自动解绑,所以解决了可能存在的内存泄漏问题。之前我们为了避免这个问题,一般有注册绑定的地方都要解绑,而 LiveData 利用生命周期感知功能解决了这一问题。

  • 解决常见的View空异常。我们通常在一个异步任务回来后需要更新 View,而此时页面可能已经被回收,导致经常会出现 View 空异常,而 LiveData 由于具备生命周期感知功能,在界面可见的时候才会进行响应,如界面更新等,如果在界面不可见的时候发起 notify,会等到界面可见的时候才进行响应更新。所以就很好的解决了空异常的问题。


LiveData 的实现上可以说是订阅发布模式+生命周期感知,对于 Activity/Fragment 等 LifecycleOwner 来说 LiveData 是观察者,监听者生命周期,而同时 LiveData 又是被观察者,我们


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


通过观察 LiveData,实现数据和 View 的关系构建。



LiveData 是粘性的,这是你在使用前需要知道的,以免因为粘性造成一些问题,使用 EventBus 的时候我们知道有一种事件模式是粘性的,特点就是消息可以在 observer 注册之前发送,当 observer 注册时,依然可接收到之前发送的这个消息。而 LiveData 天生就是粘性的,下面会讲解为什么他是粘性的,以及如果在一些业务场景上不想要 LiveData 是粘性的该怎么做。

LiveData 的实现原理

单纯的贴源码,分析源码可能比较枯燥,所以下面就尽量以抛出问题,然后解答的方式来解析 LiveData 的原理。

1.LiveData 是如何做到感知 Activity/Fragment 的生命周期?

lifecycle-aware compents 的核心就是生命周期感知,要明白 LiveData 为什么能感知生命周期,就要知道 Google 的这套生命周期感知背后的原理是什么,下面是我基于之前 lifeycycle 这套东西刚出来时候对源码进行的一个分析总结(现在的最新代码可能和之前有点出入,但是原理上基本是一样的):


首先 Activity/Fragment 是 LifecycleOwner(26.1.0 以上的 support 包中 Activity 已经默认实现了 LifecycleOwner 接口),内部都会有一个 LifecycleRegistry 存放生命周期 State、Event 等。而真正核心的操作是,每个Activity/Fragment在启动时都会自动添加进来一个Headless Fragment(无界面的Fragment),由于添加进来的 Fragment 与 Activity 的生命周期是同步的,所以当 Activity 执行相应生命周期方法的时候,同步的也会执行 Headless Fragment 的生命周期方法,由于这个这个 Headless Fragment 对我们开发者来说是隐藏的,它会在执行自己生命周期方法的时候更新 Activity 的 LifecycleRegistry 里的生命周期 State、Event, 并且 notifyStateChanged 来通知监听 Activity 生命周期的观察者。这样就到达了生命周期感知的功能,所以其实是一个隐藏的 Headless Fragment 来实现了监听者能感知到 Activity 的生命周期。


基于这套原理,只要 LiveData 注册了对 Activity/Fragment 的生命周期监听,也就拥有了感知生命周期的能力。从 LiveData 的源码里体现如下:


@MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {


owner.getLifecycle().addObserver(wrapper);//注册对 Activity/Fragment 生命周期的监听}


下面附一张当时对 Google lifecycle-aware 原理进行源码分析随手画的图:



所以到这里我们基本上已经知道了生命周期感知这套东西的原理,接下来我们就可以来看看 LiveData 的实现原理了,下我把 LiveData 的源码抽象为一张流程图来展示,下面的其他问题都可以在这张图中找到答案



可以看到,在 LiveData 所依附的 Activity/Fragment生命周期发生改变或者通过setValue()改变LiveData数据的时候都会触发notify,但是触发后,真正要走到最终的响应(即我们注册进去的 onChanged()回调)则中间要经历很多判断条件,这也是为什么 LiveData 能具有自己那些特点的原因.

2.LiveData 为什么可以避免内存泄漏?

通过上面,我们可以知道,当Activity/Fragment的生命周期发生改变时,LiveData中的监听都会被回调,所以避免内存泄漏就变得十分简单,可以看上图,当 LiveData 监听到 Activity onDestory 时则 removeObserve,使自己与观察者自动解绑。这样就避免了内存泄漏。 源码上体现如下:


@Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}activeStateChanged(shouldBeActive());}

3.LiveData 为什么可以解决 View 空异常问题?

这个问题很简单,看上图,因为 LiveData 响应(比如更新界面操作 View)只会在界面可见的时候,如果当前见面不可见,则会延迟到界面可见的时候再响应,所以自然就不会有 View 空异常的问题了。


那么 LiveData 是如何实现:


  1. 只在界面可见的时候才响应的

  2. 如果当前界面不可见,则会延迟到界面可见的时候再响应


关于问题 1,因为 LiveData 是能感知到生命周期的,所以在它回调响应的时候会加一个额外的条件,就是当前的生命周期必须是可见状态的,才会继续执行响应,源码如下:


private void considerNotify(ObserverWrapper observer) {//如果界面不可见,则不进行响应 if (!observer.mActive) {return;}if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}//如果 mVersion 不大于 mLastVersion,说明数据没有发生变化,则不进行响应 if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;//noinspection uncheckedobserver.mObserver.onChanged((T) mData);}


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


关于问题 2,在 LiveData 中有一个全局变量mVersion,而每个 observer 中有一个变量mLastVersion。当我们每次 setValue()修改一次 LiveData 的值的时候,全局的 mVersion 就会+1,这样 mVersion 就大于 mLastVersion:


@MainThreadprotected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);}


而当界面重新可见的时候,只要判断到 mVersion 大于 mLastVersion,则就会进行响应刷新 View,响应后才会更新 mLastVersion=mVersion。

4.LiveData 为什么是粘性的?

所谓粘性,也就是说消息在订阅之前发布了,订阅之后依然可以接受到这个消息,像 EventBus 实现粘性的原理是,把发布的粘性事件暂时存在全局的集合里,之后当发生订阅的那一刻,遍历集合,将事件拿出来执行。


而 LiveData 之所以本身就是粘性的,结合上面的原理图我们来分析一下,比如有一个数据(LiveData)在 A 页面 setValue()之后,则该数据(LiveData)中的全局 mVersion+1,也就标志着数据版本改变,然后再从 A 页面打开 B 页面,在 B 页面中开始订阅该 LiveData,由于刚订阅的时候内部的数据版本都是从-1 开始,此时内部的数据版本就和该LiveData全局的数据版本mVersion不一致,根据上面的原理图,B 页面打开的时候生命周期方法一执行,则会进行 notify,此时又同时满足页面是从不可见变为可见、数据版本不一致等条件,所以一进 B 页面,B 页面的订阅就会被响应一次。这就是所谓的粘性,A 页面在发消息的时候 B 页面是还没创建还没订阅该数据的,但是一进入 B 页面一订阅,之前在 A 中发的消息就会被响应。


那么有些业务场景我们是不想要这种粘性的,我们希望只有当我们订阅了该数据之后,该数据的改变才通知我们,通过上面的分析,这一点应该还是比较好办到的,只要我们订阅的时候将全局的 mVersion 同步到内部的数据版本,这样订阅时候就不会出现内部数据版本与全局的 mVersion 不一致,也就去除了粘性。我这里自定义了一个可以控制是否需要粘性的 LiveData。


具体代码见: CustomStickyLiveData

三. RxJava

RxJava 是可以实现响应式编程的另外一个手段,Rxjava 也是热度非常高的一个开源库,当然我们都知道 RxJava 一个是有订阅发布模式解耦的优点,还有其线程模型、链式写法都是其优点。


当然我个人认为不管是链式写法,还是线程模型,异或是解决回调问题都谈不上是 RxJava 的核心优点,有很多人引入 RxJava 后项目里只是利用 RxJava 方便的线程模型来做简单的异步任务,其实如果只是做异步任务,有非常多种的方式可以替代 RxJava。链式写法的话就更只是编码上的糖果了。如果在没有正确的理解 RxJava 的核心优势基础上在代码里对 RxJava 进行跟风式的滥用,很多时候你会发现,代码并没有变简洁,甚至有时候很简单的事情被搞的变复杂了。


我所理解的 RxJava 的核心优势应该是它可以对复杂逻辑进行拆分成为一个一个的 Observable 后,RxJava 的各种操作符予这些解耦的 Observable 能够合理的进行再组织的能力,并且它给予了你足够丰富的再组织能力。这种分拆再组织的能力是十分强大的,只有运用好 RxJava 这种强大的能力,才能真正意义上使你原来非常复杂的揉在一团的逻辑代码变得清晰、简洁,本质上是因为 RxJava 给你提供了这种强大方便的组织能力,我觉得有点像一种编程模式,你可以放心的将复杂的逻辑拆块,最后 RxJava 给你提供了丰富的组织、变换、串联、控制这些块的能力,只有这个时候你才会真正觉得这是个好东西,而不应该是跟风使用,但是心里也说不清楚为什么要使用。


回到文章的主题响应式,Rxjava 就不继续展开了,这篇只说关于文章主题响应式的:


看一下 RxJava 基本使用的时候一般如下:


Observable.create(new ObservableOnSubscribe<String>() {@Overridepublic void subscribe(ObservableEmitter<String> e) throws Exception {


e.onNext("通知观察者");


}}).subscribe(new io.reactivex.Observer<String>() {@Overridepublic void onSubscribe(Disposable d) {


}


@Overridepublic void onNext(String s) {Log.i("tag", "接收到消息" + s);

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
响应式编程在Android 中的一些探索,android三种开发模式