您当前的位置: 首页 >  kotlin

代码与思维

暂无认证

  • 0浏览

    0关注

    163博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

一篇文章让你读懂Kotlin的数据流问题

代码与思维 发布时间:2022-07-05 13:36:57 ,浏览量:0

一、Android分层架构

不管是早期的MVC、MVP,还是最新的MVVM和MVI架构,这些框架一直解决的都是一个数据流的问题。一个良好的数据流框架,每一层的职责是单一的。例如,我们可以在表现层(Presentation Layer)的基础上添加一个领域层(Domain Layer) 来保存业务逻辑,使用数据层(Data Layer)对上层屏蔽数据来源(数据可能来自远程服务,可能是本地数据库)。

在Android中,一个典型的Android分层架构图如下:

其中,我们需要重点看下Presenter 和 ViewModel, Presenter 和 ViewModel向 View 提供数据的机制是不同的。

  • Presenter: Presenter通过持有 View 的引用并直接调用操作 View,以此向 View 提供和更新数据。
  • ViewModel:ViewModel 通过将可观察的数据暴露给观察者来向 View 提供和更新数据。

目前,官方提供的可观察的数据组件有LiveData、StateFlow和SharedFlow。可能大家对LiveData比较熟悉,配合ViewModel可以很方便的实现数据流的流转。不过,LiveData也有很多常见的缺陷,并且使用场景也比较固定,如果网上出现了KotlinFlow 替代 LiveData的声音。那么 Flow 真的会替代 LiveData吗?Flow 真的适合你的项目吗?看完下面的分析后,你定会有所收获。

二、ViewModel + LiveData

ViewModel的作用是将视图和逻辑进行分离,Activity或者Fragment只负责UI显示部分,网络请求或者数据库操作则有ViewModel负责。ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据,让数据可在发生屏幕旋转等配置更改后继续留存。并且ViewModel不持有View层的实例,通过LiveData与Activity或者Fragment通讯,不需要担心潜在的内存泄漏问题。

而LiveData 则是一种可观察的数据存储器类,与常规的可观察类不同,LiveData 具有生命周期感知能力,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保LiveData当数据源发生变化的时候,通知它的观察者更新UI界面。同时它只会通知处于Active状态的观察者更新界面,如果某个观察者的状态处于Paused或Destroyed时那么它将不会收到通知,所以不用担心内存泄漏问题。

下面是官方发布的架构组件库的生命周期的说明:

2.1 LiveData 特性

通过前面的介绍可以知道,LiveData 是 Android Jetpack Lifecycle 组件中的内容,具有生命周期感知能力。一句话概括就是:LiveData 是可感知生命周期的,可观察的,数据持有者。特点如下:

  • 观察者的回调永远发生在主线程
  • 仅持有单个且最新的数据
  • 自动取消订阅
  • 提供「可读可写」和「仅可读」两个版本收缩权限
  • 配合 DataBinding 实现「双向绑定」
观察者的回调永远发生在主线程

因为LiveData 是被用来更新 UI的,因此 Observer 接口的 onChanged() 方法必须在主线程回调。

public interface Observer {
    void onChanged(T t);
}

背后的道理也很简单,LiveData 的 setValue() 发生在主线程(非主线程调用会抛异常),而如果调用postValue()方法,则它的内部会切换到主线程调用 setValue()。

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

可以看到,postValue()方法的内部调用了postToMainThread()实现线程的切换,之后遍历所有观察者的 onChanged() 方法。

仅持有单个且最新数据

作为数据持有者,LiveData仅持有【单个且最新】的数据。单个且最新,意味着 LiveData 每次只能持有一个数据,如果有新数据则会覆盖上一个。并且,由于LiveData具备生命周期感知能力,所以观察者只会在活跃状态下(STARTED 到 RESUMED)才会接收到 LiveData 最新的数据,在非活跃状态下则不会收到。

自动取消订阅

可感知生命周期的重要优势就是可以自动取消订阅,这意味着开发者无需手动编写那些取消订阅的模板代码,降低了内存泄漏的可能性。背后的实现逻辑是在生命周期处于 DESTROYED 时,移除观察者。

@Override
public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
    if (currentState == DESTROYED) {
        removeObserver(mObserver);
        return;
    }
    ... //省略其他代码
}

提供「可读可写」和「仅可读」两种方式

LiveData 提供了setValue() 和 postValue()两种方式来操作实体数据,而为了细化权限,LiveData又提供了mutable(MutableLiveData) 和 immutable(LiveData) 两个类,前者「可读可写」,后者则「仅可读」。

配合 DataBinding 实现「双向绑定」

LiveData 配合 DataBinding 可以实现更新数据自动驱动UI变化,如果使用「双向绑定」还能实现 UI 变化影响数据的变化功能。

2.2 LiveData的缺陷

正如前面说的,LiveData有自己的使用场景,只有满足使用场景才会最大限度的发挥它的功能,而下面这些则是在设计时将自带的一些缺陷:

  • value 可以是 nullable 的
  • 在 fragment 订阅时需要传入正确的 lifecycleOwner
  • 当 LiveData 持有的数据是「事件」时,可能会遇到「粘性事件」
  • LiveData 是不防抖的
  • LiveData 的 transformation 需要工作在主线程
value 可以是 nullable 的

由于LiveData的getValue() 是可空的,所以在使用时应该注意判空,否则容易出现空指针的报错。

@Nullable
public T getValue() {
    Object data = mData;
    if (data != NOT_SET) {
        return (T) data;
    }
    return null;
}

传入正确的 lifecycleOwner

Fragment 调用 LiveData的observe() 方法时传入 this 和 viewLifecycleOwner 的含义是不一样的。因为Fragment与Fragment中的View的生命周期并不一致,有时候我们需要的让observer感知Fragment中的View的生命周期而非Fragment。

粘性事件

粘性事件的定义是,发射的事件如果早于注册,那么注册之后依然可以接收到的事件,这一现象称为粘性事件。解决办法是:将事件作为状态的一部分,在事件被消费后,不再通知观察者。推荐两种解决方式:

  • KunMinX/UnPeek-LiveData
  • 使用kotlin 扩展函数和 typealias 封装解决「粘性」事件的 LiveData
默认不防抖

当setValue()/postValue() 传入相同的值且多次调用时,观察者的 onChanged() 也会被多次调用。不过,严格来讲,这也不算一个问题,我们只需要在调用 setValue()/postValue() 前判断一下 vlaue 与之前是否相同即可。

transformation 工作在主线程

有些时候,我们需要对从Repository 层得到的数据进行处理。例如,从数据库获得 User列表,我们需要根据 id 获取某个 User, 那么就需要用到MediatorLiveData 和 Transformatoins 来实现。

  • Transformations.map
  • Transformations.switchMap

并且,map 和 switchMap 内部均是使用 MediatorLiveData的addSource() 方法实现的,而该方法会在主线程调用,使用不当会有性能问题。

@MainThread
public  void addSource(@NonNull LiveData source, @NonNull Observer            
关注
打赏
1665387627
查看更多评论
0.0425s