Jetpack LiveData 这个组件对于现在的 Android 开发者来说应该是很熟悉的了吧?那么,你是否也曾经困扰于以下的业务场景呢?(哈哈哈哈哈,你必须困扰,不然本篇文章我也吹不下去了。当然,即使你没有遇到过这种业务场景,本篇文章也为你以后遇到相同问题时(大概率会遇到)提供了解决方案和解决思路)
这里先放下 GitHub 链接:https://github.com/leavesC/EventLiveData
这里先来假设两种场景,先让读者体会到 LiveData 相对实际业务上具有的局限性
假设当前你的 App 包含一个「圈子列表页面」,每个圈子 item 包含了一个按钮用于改变对此圈子的「关注状态」,点击 item 可以跳转到「圈子详情页」,在详情页也包含一个按钮用于改变圈子的关注状态
然后,产品经理要求:当从「圈子列表页」跳转到「圈子详情页」时,在「圈子详情页」改变了关注状态后,回到「圈子列表页」时也要实时显示当前的最新关注状态
此时,凭直觉我们就会写这样一行代码:
companion object { //String 表示圈子ID,Boolean 表示对该圈子的关注状态 val focusLiveData = MutableLiveData<Pair<String, Boolean>>() } 复制代码
声明一个全局静态变量 focusLiveData
,在「圈子详情页」如果改变了关注状态,则向 focusLiveData postValue
,透传出当前最新的关注状态。然后在「圈子列表页面」对 focusLiveData
进行监听,当接收到事件回调时则自动刷新列表从而拿到最新的关注状态
依靠直觉写出以上代码后,产品经理的需求我们也完美地实现了?并没有!因为此时又带来了一个新的问题!正常情况下当我们进入「圈子列表页面」时是会自动通过网络请求从服务器拉取数据的,而如果此时 focusLiveData
本身就是有值的,那我们刚进入「圈子列表页面」时就会收到旧值导致的事件回调!!!从而导致页面被刷新了两次,此时测试可能就会来给你提 bug 了。「而这问题的出现根源就是因为 focusLiveData 会持有旧值,相当于使得 Activity 收到了一个粘性事件」
如果 UI 设计师对「圈子列表页」的关注按钮设计了一个非常花里胡哨的动画,在关注时要执行一个几百毫秒的动画,那么按照以上的代码逻辑,当用户在「圈子详情页关注了圈子后」,回到「圈子列表页」时用户就会非常奇怪地看到关注按钮自己执行了动画。然后此时测试又来提 bug 了,要求在「圈子详情页」改变了关注状态后,「在圈子列表页不要执行动画」,直接改变关注状态即可。此时我们就不得不对动画的执行条件做区分了,从而加大我们业务的复杂度。「而这问题的出现根源就是因为 LiveData 在绑定了生命周期的情况下只会在 Activity onResume 的时候才进行回调,而此时用户都已经可以看到 Activity 处于前台了」
很好,基于以上现状,我们再来总结下我们的需求:
基于以上需求,我实现了 EventLiveData 来扩展原生 LiveData 的功能,在继续保持原生 LiveData 原有功能的同时,也扩展了以下几个功能入口:
首先,你可以把 EventLiveData 当做原生的 LiveData 来使用,以下方法和 LiveData 完全一样:
/** * 在生命周期安全的整体保障上和 LiveData 完全一样 * 在 onResume 时接收 Observer 回调,并在 onDestroy 前自动移除监听 * @param owner * @param observer */ @MainThread fun observe(owner: LifecycleOwner, observer: Observer<T>) { ··· } /** * 不具备生命周期安全的保障,使用上和 LiveData 完全一样 * @param observer */ @MainThread fun observeForever(observer: Observer<T>) { ··· } 复制代码
如果你希望在 observe 后不会收到旧值,则可以使用 observeEvent
、observeEventForever
两个方法,两者的区别仅在于是否绑定了生命周期:
/** * 在生命周期安全的整体保障上和 LiveData 完全一样 * 在 onResume 时接收 Observer 回调,并在 onDestroy 前自动移除监听 * 但此方法不会向 Observer 回调旧值,即 EventLiveData 只会向 Observer 回调在调用 observeEvent 之后收到的值 * @param owner * @param observer */ @MainThread fun observeEvent(owner: LifecycleOwner, observer: Observer<T>) { ··· } /** * 不具备生命周期安全的保障 * 此方法不会向 Observer 回调旧值,即 EventLiveData 只会向 Observer 回调在调用 observeEvent 之后收到的值 * @param observer */ @MainThread fun observeEventForever(observer: Observer<T>) { ··· } 复制代码
如果你希望在 onCreate 之后和 onDestory 之前均能收到数据回调,则可以使用 observeAlive
方法
/** * 相比 LiveData 会具备更长的生命周期 * 在 onCreate 之后和 onDestroy 之前均能收到 Observer 回调,并在 onDestroy 时自动移除监听 * @param owner * @param observer */ @MainThread fun observeAlive(owner: LifecycleOwner, observer: Observer<T>) { ··· } 复制代码
如果你即不想收到旧值,又希望在 onCreate 之后和 onDestory 之前均能收到数据回调,则可以使用 observeAliveEvent
/** * 相比 LiveData 会具备更长的生命周期 * 在 onCreate 之后和 onDestroy 之前均能收到 Observer 回调,并在 onDestroy 时自动移除监听 * 但此方法不会向 Observer 回调旧值,即 EventLiveData 只会向 Observer 回调在调用 observeEvent 之后收到的值 * @param owner * @param observer */ @MainThread fun observeAliveEvent(owner: LifecycleOwner, observer: Observer<T>) { ··· } 复制代码
向 EventLiveData 传递值的入口统一为了 postValue(value: T)
方法,内部会根据调用者所在线程自动进行线程切换
fun postValue(value: T) { ··· } 复制代码
EventLiveData 是基于原生 LiveData 来实现的,在理解了 LiveData 的实现原理后,其实我们就可以比较简单地来改造其源码实现自定义要求了。不了解 LiveData 实现原理的同学可以先看下我写的另外一篇文章:从源码看 Jetpack(3)-LiveData源码解析
这里再来简单介绍下 EventLiveData 的实现原理
LiveData 内部包含一个 mVersion
来记录「当前值的新旧程度」,当外部传递了新值时(不管是 setValue 还是 postValue),mVersion 均会递增+1
@MainThread private fun setValue(value: T) { assertMainThread( "setValue" ) mVersion++ mData = value dispatchingValue(null) } 复制代码
同时 ObserverWrapper 内部包含一个 mLastVersion
用于标记 Observer 内最后一个被回调的 value 的新旧程度
private abstract class ObserverWrapper { //外部传进来的对 LiveData 进行数据监听的 Observer final Observer<? super T> mObserver; //用于标记 mObserver 是否处于活跃状态 boolean mActive; //用于标记 Observer 内最后一个被回调的 value 的新旧程度 int mLastVersion = START_VERSION; ObserverWrapper(Observer<? super T> observer) { mObserver = observer; } } 复制代码
而 considerNotify
方法就是根据 mLastVersion 的大小来决定是否需要向外部传进来 Observer 回调值,那么我们只要控制 Observer 的 mLastVersion 的初始值大小不就可以避免旧值的通知了吗?
private void considerNotify(ObserverWrapper observer) { ··· if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); } 复制代码
再然后,LifecycleBoundObserver 的 shouldBeActive()
方法就限制了「只有当 Lifecycle 的当前状态是 STARTED 或者 RESUMED 时才进行数据回调」,那么我们只要改变此限制条件,就可以「增大 Observer 的有效生命周期范围了」
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { @NonNull final LifecycleOwner mOwner; LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) { super(observer); mOwner = owner; } @Override boolean shouldBeActive() { //只有当 Lifecycle 的当前状态是 STARTED 或者 RESUMED 时 //才认为 Lifecycle 是处于活跃状态 return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } } 复制代码
EventLiveData 的实现原理就是基于以上两点,虽然听起来简单,但这都是基于了解了 LiveData 的实现逻辑的基础上,不熟悉的同学可以先看下我对 LiveData 的源码解析文章
EventLiveData 已开源到 GitHub:https://github.com/leavesC/EventLiveData ,且已发布到第三方代码托管库,读者可以直接以以下方法来引入依赖
添加远程依赖库地址
allprojects { repositories { maven { url 'https://jitpack.io' } } } 复制代码
引入依赖:
dependencies { implementation 'com.github.leavesC:EventLiveData:0.1.0' } 复制代码
「感谢阅读」