一道面试题:介绍一下 LiveData 的 postValue ?

简介: 一道面试题:介绍一下 LiveData 的 postValue ?

很多面试官喜欢会就一个问题不断深入追问。

例如一个小小的 LiveData 的 postValue,就可能会问出一连串问题:

image.png

postValue 与 setValue

postValuesetValue 一样都是用来更新 LiveData 数据的方法:

  • setValue 只能在主线程调用,同步更新数据
  • postValue 可在后台线程调用,其内部会切换到主线程调用 setValue
liveData.postValue("a");
liveData.setValue("b");

上面代码,a 在 b 之后才被更新。

postValue 收不到通知

postValue 使用不当,可能发生接收到数据变更的通知:

If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.

如上,源码的注释中明确记载了,当连续调用 postValue 时,有可能只会收到最后一次数据更新通知。

梳理源码可以了解其中原由:

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

mPendingData 被成功赋值 value 后,post 了一个

Runnable

mPostValueRunnable 的实现如下:

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        setValue((T) newValue);
    }
};
  • postValue 将数据存入 mPendingDatamPostValueRunnable 在UI线程消费mPendingData
  • 在 Runnable 中 mPendingData 值还没有被消费之前,即使连续 postValue , 也不会 post 新的 Runnable
  • mPendingData 的生产 (赋值) 和消费(赋 NOT_SET) 需要加锁 这也就是当连续 postValue 时只会收到最后一次通知的原因。

源码梳理过了,但是为什么要这样设计呢?

为什么 Runnable 只 post 一次?

mPenddingData 中有数据不断更新时,为什么 Runnable 不是每次都 post,而是等待到最后只 post 一次?

一种理解是为了兼顾性能,UI只需显示最终状态即可,省略中间态造成的频发刷新。这或许是设计目的之一,但是一个更为合理的解释是:即使 post 多次也没有意义,所以只 post 一次即可

我们知道,对于 setValue 来说,连续调用多次,数据会依次更新:

如下,订阅方一次收到 a b 的通知

liveData.setValue("a");
liveData.setValue("b");

通过源码可知,dispatchingValue() 中同步调用 Observer#onChanged(),依次通知订阅方:

//setValue源码
@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}

但对于 postValue,如果当 value 变化时,我们立即post,而不进行阻塞

protected void postValue(T value) {
    mPendingData = value;
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
private final Runnable mPostValueRunnable = new Runnable() {
    public void run() {
        setValue((T) mPendingData);
    }
};
liveData.postValue("a")
liveData.postValue("b")

由于线程切换的开销,连续调用 postValue,收到通知只能是b、b,无法收到a。

因此,post 多次已无意义,一次即可。

为什么要加读写锁?

前面已经知道,是否 post 取决于对 mPendingData 的判断(是否为 NOT_SET)。因为要在多线程环境中访问 mPendingData ,不加读写锁无法保证其线程安全。

protected void postValue(T value) {
    boolean postTask = mPendingData == NOT_SET; // --1
    mPendingData = value; // --2
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
private final Runnable mPostValueRunnable = new Runnable() {
    public void run() {
        Object newValue = mPendingData;
        mPendingData = NOT_SET; // --3
        setValue((T) newValue);
    }
};

如上,如果在 1 和 2 之间,执行了 3,则 2 中设置的值将无法得到更新

使用RxJava替换LiveData

如何避免在多线程环境下不漏掉任何一个通知? 比较好的思路是借助 RxJava 这样的流式框架,任何数据更新都以数据流的形式发射出来,这样就不会丢失了。

fun <T> Observable<T>.toLiveData(): LiveData<T> = RxLiveData(this)
class RxLiveData<T>(
    private val observable: Observable<T>
) : LiveData<T>() {
    private var disposable: Disposable? = null
    override fun onActive() {
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                setValue(it)
            }, {
                setValue(null)
            })
    }
    override fun onInactive() {
        disposable?.dispose()
    }
}

最后

想要保证事件在线程切换过程中的顺序性和完整性,需要使用RxJava这样的流式框架。

有时候面试官会使用追问的形式来挖掘候选人的技术深度,所以大家在准备面试时要多问自己几个问什么,知其然并知其所以然。

当然,我也不赞同这种刨根问底式的拷问方式,尤其是揪着一些没有实用价值的细枝末节不放。所以本文也是提醒广大面试官,挖掘深度的同时要注意分寸,不能以将候选人难倒为目标来问问题。

目录
相关文章
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
61 4
|
2月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
93 2
|
4月前
|
Java C++
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
这篇文章讨论了Java单继承的设计原因,指出Java不支持多继承主要是为了避免方法名冲突等混淆问题,尽管Java类不能直接继承多个父类,但可以通过接口和继承链实现类似多继承的效果。
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
|
2月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
37 0
|
4月前
|
存储 安全 Java
这些年背过的面试题——Java基础及面试题篇
本文是技术人面试系列Java基础及面试题篇,面试中关于Java基础及面试题都需要了解哪些内容?一文带你详细了解,欢迎收藏!
|
4月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
4月前
|
Java
【Java基础面试三十七】、说一说Java的异常机制
这篇文章介绍了Java异常机制的三个主要方面:异常处理(使用try、catch、finally语句)、抛出异常(使用throw和throws关键字)、以及异常跟踪栈(异常传播和程序终止时的栈信息输出)。