其他分享
首页 > 其他分享> > Jetpack LiveData的设计理念及改进

Jetpack LiveData的设计理念及改进

作者:互联网

在这里插入图片描述

一、架构指南

在日常的开发中,我们经常会讲到 MVC、MVP、MVVM 等多种开发模式,这其实都是应用架构的不同呈现方式,你目前又是使用的什么应用架构呢?

一个好的架构,其至少应该遵循两个原则

关于第一点。对于一个移动设备来说,其拥有的资源是固定且极其有限的,系统可能会随时终止某些应用进程以便为前台进程腾出内存空间。而且,即使是对于前台进程来说,我们也并非拥有ActivityFragment的完全所有权,系统也可能会随时因为内存空间不足或者系统配置更改等意外情况而销毁它们。做到关注点分离,就是将各个层次的职责划分开,避免将用户数据和界面的生命周期强绑定,这样当意外情况发生时用户数据才不会随之一起被销毁

关于第二点。通过模型驱动界面,即界面对模型来说应该是相当于一个观察者而独立存在,界面不应该直接持有数据。界面通过观察数据的变化来驱动自身,模型也可以通过改变自身来驱动界面更改。这里指的模型最好是持久性模型,即模型的生命周期需要比界面甚至应用进程更加长,典型代表即 Jetpack 中的 ViewModel 和 Room。这样当有意外情况发生时,用户也不会丢失数据,我们可以在随后就为用户恢复数据

Google 推荐的应用架构图如下所示

当中,每个组件仅依赖于其下一级的组件。ViewModel 就是关注点分离原则的一个具体实现,是作为用户数据的承载体处理者而存在的,Activity/Fragment 仅依赖于 ViewModel,ViewModel 就用于响应界面层的输入和驱动界面层变化,Repository 用于为 ViewModel 提供一个单一的数据来源及数据存储域,Repository 可以同时依赖于持久性数据模型和远程服务器数据源

二、LiveData 的优势

本文想要讨论的就是 ViewModel 所包含的 LiveData

从 Google 官方推荐的应用架构图可以看到,LiveData 是包含在 ViewModel 中的。LiveData 是一种可观察的数据存储器,Activity/Fragment 是观察者,LiveData 是被观察者。LiveData 具有生命周期感知能力,当我们向 LiveData 注册了一个和 LifecycleOwner 相绑定的 Observer 时,如果 LifecycleOwner 的生命周期处于STARTEDRESUMED 状态,则认为该观察者当前处于活跃状态,此时 LiveData 才会向观察者发送事件通知,非活跃状态的观察者不会收到任何事件通知。且当 LifecycleOwner 的状态变为DESTROYED时,LiveData 会自动解除和观察者之间的绑定关系,以防止内存泄漏和过多的内存消耗。所以说,LiveData 具有生命周期感知能力,Activity/Fragment 无需和 LiveData 创建明确且严格的依赖路径

ViewModel 和 LiveData 可以看做是对关注点分离通过模型驱动界面原则的一个共同实现,ViewModel 提供了让用户数据独立于界面而存在的能力,LiveData 提供了安全地通知并驱动界面变化的能力

三、LiveData 的缺陷

LiveData 的设计初衷就决定了其具有以下三点缺陷(或者说特性):

  1. 只在 Observer 至少处于 STARTED 状态时才能收到事件通知。Activity 只有在 onStart 方法后且 onPause 方法前才能收到事件通知
  2. LiveData 是黏性的。假设存在一个静态的 LiveData 变量,且已经包含了数据,对其进行监听的 Activity 都会收到其当前值的回调通知,即收到了黏性消息。这个概念就类似于 EventBus 中的 StickyEvent
  3. 中间值可以被新值直接掩盖。当 Activity 处于后台时,如果 LiveData 先后接收到了多个值,那么当 Activity 回到前台时也只会收到最新值的一次回调,中间值直接被掩盖了,Activity 完全不会感受到中间值的存在

以上三点特性都是由于界面层的特点来决定的:

四、LiveData 作为消息通知组件

如果将 LiveData 单纯地作为界面层状态更新的载体来看待的话,那么以上三点特性就挺合情合理的了。但如果我们是将 LiveData 作为应用全局的消息通知组件的话,这三个特性就会给我们带来困扰了

相信很多开发者都尝试过将一个 LiveData 实例声明为静态变量,然后多个 Activity 通过同时监听该 LiveData 来实现数据通信。这种方式的优点是:能够以非常简单的方式来实现跨页面通信,同时也保障了生命周期安全。缺点是:在 Activity 处于 onStop 状态时无法收到通知,会收到黏性消息这种脏数据

在 Activity 处于 onStop 状态时收到通知有什么意义呢?收到了黏性消息又会导致什么问题呢? 这可以通过假设一个需求来说明

假设当前你的 App 包含一个圈子列表页面,每个圈子 item 包含了一个按钮用于改变对此圈子的关注状态,当点击关注后就会向用户展示一个几百毫秒的动画效果。点击 item 可以跳转到圈子详情页,在详情页也包含一个按钮用于改变圈子的关注状态

现在,产品要求两个页面间的关注状态是要能够实时统一的,即在圈子详情页改变关注状态后,圈子列表页面也要跟着一起改变关注状态。为了实现这个效果,可以声明一个全局的静态变量来实现跨页面通知圈子关注状态的变化

object FocusRepository {

    //String 表示圈子ID,Boolean 表示对该圈子的关注状态
    val focusLiveData = MutableLiveData<Pair<String, Boolean>>()

}

class CircleListActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_circle_list)
        //建立监听
        FocusRepository.focusLiveData.observe(this, Observer {

        })
    }

}

class CircleDetailsActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_circle_details)
        onCircleFocusStateChanged("100", true)
    }

    private fun onCircleFocusStateChanged(circleId: String, focused: Boolean) {
        FocusRepository.focusLiveData.value = Pair(circleId, focused)
    }

}

这种方式就会导致三个问题:

五、EventLiveData

考虑到 LiveData 不那么适合用做应用全局的消息通知组件,所以我就基于其源码实现了一个改良版的 EventLiveData,以此来解决 LiveData 的缺陷。EventLiveData 在使用上基本 LiveData 一样,我只是对其进行了功能扩展

发送消息:

        val eventLiveData = EventLiveData<String>()

        //主线程调用
        eventLiveData.setValue("leavesC")
        //子线程调用
        eventLiveData.postValue("leavesC")
        //任意线程都可以调用,内部会自动判断线程
        eventLiveData.submitValue("leavesC")

不接收黏性消息:

        //不接收黏性消息
        //在 onResume 之后和 onDestroy 之前均能收到 Observer 回调
        eventLiveData.observe(LifecycleOwner, Observer {

        })

        //不接收黏性消息
        //在 onCreate 之后和 onDestroy 之前均能收到 Observer 回调
        eventLiveData.observe(LifecycleOwner, Observer {

        }, false)

接收黏性消息:

        //接收黏性消息
        //在 onResume 之后和 onDestroy 之前均能收到 Observer 回调
        eventLiveData.observeSticky(LifecycleOwner, Observer {

        })

        //接收黏性消息
        //在 onCreate 之后和 onDestroy 之前均能收到 Observer 回调
        eventLiveData.observeSticky(LifecycleOwner, Observer {

        }, false)

不和生命周期绑定:

        //不接收黏性消息
        eventLiveData.observeForever(Observer {

        })

        //接收黏性消息
        eventLiveData.observeForeverSticky(Observer {

        })

六、引入依赖

EventLiveData 已托管到 jitpack,可以直接远程依赖。GitHub 地址:https://github.com/leavesC/EventLiveData

	allprojects {
		repositories {
			maven { url 'https://jitpack.io' }
		}
	}
	
	dependencies {
	    implementation 'com.github.leavesC:EventLiveData:1.0.0'
	}

七、实现原理

EventLiveData 是基于 LiveData 的源码来改造实现的,在理解了 LiveData 的设计理念和实现原理后来进行自定义其实就非常简单了,这里就简单说下我的实现思路

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);
        }

    }

八、参考资料

九、相关文章推荐

从源码看 Jetpack(1)-Lifecycle 源码解析

从源码看 Jetpack(2)-Lifecycle 衍生

从源码看 Jetpack(3)-LiveData 源码解析

从源码看 Jetpack(4)-LiveData 衍生

从源码看 Jetpack(5)-Startup 源码详解

从源码看 Jetpack(6)-ViewModel 源码详解

从源码看 Jetpack(7)-SavedStateHandle 源码详解

标签:界面,理念,Observer,黏性,Jetpack,LiveData,源码,Activity
来源: https://blog.csdn.net/new_one_object/article/details/110749752