自己动手改造 Jetpack LiveData

时间:2022-07-22
本文章向大家介绍自己动手改造 Jetpack LiveData,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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 处于前台了


很好,基于以上现状,我们再来总结下我们的需求:

  1. 希望 Activity 在 observe 一个 LiveData 后不会收到其之前的旧值,即 Activity 只希望收到 LiveData 在 observe 后新改变的值
  2. 希望 Activity 在 onCreate 之后且 onDestory 之前均能收到 LiveData 的回调值,而不仅仅是被限制在 onResume 状态。在 LiveData 的开发者看来,只有处于 onResume 状态时 Activity 对于用户来说才是可见的,此时进行数据回调才是最有意义的;当然,大部分情况的确如此,但按我上面所说的业务场景,有时候我们还是希望能在 Activity 回到前台前就完成数据回调及相应的 UI 更新
  3. 业务场景是复杂多变的,希望 LiveData 能够具备以上两种功能的组合,那我们才能按需取用
  4. 在具备以上功能的同时,还希望 LiveData 也还能继续拥有其原有的功能特性,即希望以上所述的三点功能是在原有的 LiveData 的基础上进行扩展,而不仅仅是直接取代

二、方法介绍

基于以上需求,我实现了 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>) {
        ···
    }
2.1、observeEvent 、 observeEventForever

如果你希望在 observe 后不会收到旧值,则可以使用 observeEventobserveEventForever 两个方法,两者的区别仅在于是否绑定了生命周期:

   /**
     * 在生命周期安全的整体保障上和 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>) {
        ···
    }
2.2、observeAlive

如果你希望在 onCreate 之后和 onDestory 之前均能收到数据回调,则可以使用 observeAlive 方法

    /**
     * 相比 LiveData 会具备更长的生命周期
     * 在 onCreate 之后和 onDestroy 之前均能收到 Observer 回调,并在 onDestroy 时自动移除监听
     * @param owner
     * @param observer
     */
    @MainThread
    fun observeAlive(owner: LifecycleOwner, observer: Observer<T>) {
        ···
    }
2.3、observeAliveEvent

如果你即不想收到旧值,又希望在 onCreate 之后和 onDestory 之前均能收到数据回调,则可以使用 observeAliveEvent

    /**
     * 相比 LiveData 会具备更长的生命周期
     * 在 onCreate 之后和 onDestroy 之前均能收到 Observer 回调,并在 onDestroy 时自动移除监听
     * 但此方法不会向 Observer 回调旧值,即 EventLiveData 只会向 Observer 回调在调用 observeEvent 之后收到的值
     * @param owner
     * @param observer
     */
    @MainThread
    fun observeAliveEvent(owner: LifecycleOwner, observer: Observer<T>) {
        ···
    }
2.4、postValue

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'
    }

感谢阅读