LiveData 原理分析

认识 LiveData

LiveData 是一种可观察的数据存储器类。同时它具备生命周期感知能力,确保只更新处于活跃生命周期状态的观察者组件。

LiveData 具有以下优势:

  • 确保界面符合数据状态:当数据发生变化时,就会通知观察者。观察者监听到有数据变化,就会更新界面。
  • 不会发生内存泄漏,不需要手动解除观察者:因为具备了生命周期感知能力,在生命周期宿主状态变为 DESTROYED 后会自动移除观察者。
  • 不会因为生命周期宿主状态不活跃而导致崩溃:当生命周期宿主处于非活跃状态时,不会接收到任何 LiveData 事件。
  • 数据始终保持最新状态:当数据更新时,若生命周期宿主处于非活跃状态,则不会接收到任何 LiveData 事件,在生命周期宿主变为活跃状态时会接收最新数据。

使用 LiveData

  1. 添加对应的依赖:

    livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata", version = "2.8.3" }

如果项目使用了 Compose,记得添加以下依赖,针对 LiveData 提供了很多适用于 Compose 场景下的扩展方法:

androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version = "1.7.5" }
  1. 创建 LiveData 实例,指定源数据类型:LiveData 通常配合 ViewModel 使用,存储在 ViewModel 对象中,通过 setValue()/postValue() 更新数据

    class LiveDataViewModel : ViewModel() {
    private val _data: MutableLiveData<Int> = MutableLiveData<Int>(0)
    val data: LiveData<Int> get() = _data

     fun updateValue() {
         _data.value = (_data.value ?: 0) + 1
     }
    

    }

LiveData 可设置初始值,上面的例子中设置了初始值为0,如果没有设置初始值,则初始为 null。

  1. 创建 Observer 实例:通过 observe 方法,传入生命周期宿主和 Observer 实例,监听数据变化,如果 LiveData 设置了初始值,则初始值也会调用一次 observe 方法

    viewModel.data.observe(this) {
    }

分析 LiveData 原理

LiveData 如何感知生命周期的变化?

对于 Android 中 Lifecycle 不熟悉的,可以先看这篇文章:Android Jetpact Lifecycle 解析

LiveData的原理是观察者模式,通过 LiveData#observe 方法监听 LiveData 数据变化,需要传入两个参数,第一个参数为生命周期宿主 LifecycleOwner,第二个参数为 Observer 观察者。

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    // 要求在主线程执行
    assertMainThread("observe");
    // 宿主生命周期已处于 DESTROYED 状态,直接忽略
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    // 将 LifecycleOwner 和 Observer 包装成 LifecycleBoundObserver
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    // 加入观察者集合中
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    // 已经添加到观察者集合中且绑定的 LifecycleOwner 不是传进来的 owner
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    // 已经添加到观察者集合中且已经注册owner
    if (existing != null) {
        return;
    }
    // 对 Lifecycle 注册观察者
    owner.getLifecycle().addObserver(wrapper);
}

首先要求在主线程执行,如果宿主生命周期已处于 DESTROYED 状态,直接忽略。然后将 LifecycleOwner 和 Observer 包装成 LifecycleBoundObserver,这是最重要的一步,LifecycleBoundObserver 加入到了观察者集合 mObservers 中,具备了观察数据更新的能力,又对 LifecycleOwner 的 Lifecycle 注册观察,具备了生命周期感知的能力。

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 时,认为是活跃状态
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    // 生命周期事件回调
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        // DESTROYED 状态,移除观察者,避免内存泄漏
        if (currentState == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        Lifecycle.State prevState = null;
        // 循环更新状态,保证状态是最新的
        while (prevState != currentState) {
            prevState = currentState;
            // 修改活跃状态,若为活跃状态则进行数据分发
            activeStateChanged(shouldBeActive());
            currentState = mOwner.getLifecycle().getCurrentState();
        }
    }

    @Override
    boolean isAttachedTo(LifecycleOwner owner) {
        return mOwner == owner;
    }

    @Override
    void detachObserver() {
        mOwner.getLifecycle().removeObserver(this);
    }
}

LifecycleBoundObserver 是继承 ObserverWrapper 的包装类,实现了 LifecycleEventObserver 接口,可以收到 Lifecycle 生命周期事件的回调。

当 Lifecycle 生命周期变化时,回调 onStateChanged 方法,获取到最新的生命周期状态,若为 DESTROYED 状态,移除观察者,避免内存泄漏,否则通过循环更新状态,保证状态是最新的。

当 Lifecycle 状态大于等于 STARTED 时,也就是为 STARTED 和 RESUMED 时,认为是活跃状态,会进行数据分发。

@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {
    assertMainThread("observeForever");
    AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing instanceof LiveData.LifecycleBoundObserver) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    wrapper.activeStateChanged(true);
}

如果想在数据更新的时候让 Observer 立即得到通知,也就是说忽略生命周期状态,可以使用 LiveData#observeForever 方法,不需要 LifecycleOwner,同时也就需要开发者手动 removeObserver。

observeForever 中将 Observer 包装成 AlwaysActiveObserver,AlwaysActiveObserver 也是 ObserverWrapper 的实现类,其方法 shouldBeActive 固定返回 true,意味着只要有数据变化就会进行数据分发。

private class AlwaysActiveObserver extends ObserverWrapper {

    AlwaysActiveObserver(Observer<? super T> observer) {
        super(observer);
    }

    @Override
    boolean shouldBeActive() {
        return true;
    }
}

LiveData 更新数据过程

LiveData 更新数据有两种方式:setValue 和 postValue,setValue 只能用于主线程,postValue 可用于子线程。

    @MainThread
    protected void setValue(T value) {
        // 只能在主线程
        assertMainThread("setValue");
        // 更新当前 LiveData 数据的版本号
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

mVersion 用于标记 LiveData 数据的版本,初始为-1,每更新一次数据,mVersion 加 1。

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                    //如果 mDispatchInvalidated 为 true,则中断继续遍历过程
                    //用新值重新循环一遍
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

dispatchingValue 方法中,用两个全局的布尔变量 mDispatchingValue 和 mDispatchInvalidated 实现了新旧值判断、旧值舍弃、新值重新全局发布的逻辑。其中需要注意 mObservers 的遍历过程,由于每遍历一个 item 都会检查一次当前的 value 是否已经过时,是的话则中断遍历,所以会存在仅有部分 Observer 收到了旧值的情况。

    private void considerNotify(ObserverWrapper observer) {
        //如果 observer 处于非活跃状态,则直接返回
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //根据 observer 自己的 value 版本号 mLastVersion 来决定是否需要向其进行更新
        //为了避免重复向某个 observer 更新
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

postValue 方法不限定调用者所在线程,不管是主线程还是子线程都可以调用,因此是存在多线程竞争的可能性的。

    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(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 方法将更新的 value 值保存在变量 mPendingData 上,然后把 Runnable 对象 mPostValueRunnable 抛到主线程,其 run 方法中还是使用的 setValue 方法。由于最终更新值的操作仍然是在主线程,所以在 mPostValueRunnable 被执行前,所有通过 postValue 方法更新的 value 值都会被保存在变量 mPendingData 上,且只会保留最后一个,直到 mPostValueRunnable 被执行后 mPendingData 才会被重置,所以使用 postValue 方法在多线程同时调用或者单线程连续调用的情况下是存在丢值(外部的 Observer 只能接收到最新值)的可能性的。

Livedata 是否具有粘性,及原因

所谓粘性就是新的观察者被老的数据值通知的现象,Livedata 是具有粘性的。

public abstract class LiveData<T> {
    // 存储数据的字段
    private volatile Object mData;
    // 数据的版本号
    private int mVersion;
}

LiveData 的数据被存储在内部的 mData 变量中,直到有更新的数据覆盖,所以数据是持久的。LiveData 使用变量 mVersion 来标识数据的版本,用于判断当前数据是否是最新的。

    @MainThread
    protected void setValue(T value) {
        // 只能在主线程
        assertMainThread("setValue");
        // 更新当前 LiveData 数据的版本号
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

LiveData 有两种场景会进行数据的分发:一种是通过 setValue 方法更新 LiveData 数据,postValue 方法最终也是通过 setValue 方法更新 LiveData 数据的,在 setValue 方法里通过 dispatchingValue 方法进行数据的分发,会遍历所有观察者并进行分发;另一种是新增观察者或者观察者的生命周期发生变化(至少为 STARTED),在观察者 ObserverWrapper#activeStateChanged 方法里也是通过dispatchingValue 方法进行数据的分发,此时只会给单个观察者进行数据分发。

    private abstract class ObserverWrapper {
        final Observer<? super T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION;

        ObserverWrapper(Observer<? super T> observer) {
            mObserver = observer;
        }

        abstract boolean shouldBeActive();

        boolean isAttachedTo(LifecycleOwner owner) {
            return false;
        }

        void detachObserver() {
        }

        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
            changeActiveCounter(mActive ? 1 : -1);
            if (mActive) {
                dispatchingValue(this);
            }
        }
    }

    private void considerNotify(ObserverWrapper observer) {
        //如果 observer 处于非活跃状态,则直接返回
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //根据 observer 自己的 value 版本号 mLastVersion 来决定是否需要向其进行更新
        //为了避免重复向某个 observer 更新
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

considerNotify 方法负责将数据分发给观察者,会进行三次判断,首先判断数据观察者是否活跃,然后判断数据观察者绑定的生命周期宿主是否活跃(因为可能生命周期宿主的活跃状态还没有同步到观察者的活跃状态),最后判断数据观察者的数据版本号是否是最新的,经过这三次判断之后,会将最新的数据分发给观察者。

因为新观察者的数据版本号初始值为-1,必然小于 LiveData 的数据版本号,新观察者被添加时会触发一次数据数据分发,必然会接收到老的数据,所以 LiveData 是具有粘性的。

LiveData 的粘性问题,及解决方法

LiveData 的粘性可能会导致一些重复触发的问题,比如常见的横竖屏切换所导致的 Activity 的重建,Activity 内的观察者也会被重新创建且添加,就会再次收到 LiveData 的数据,一些业务可能在此场景下会导致重复触发的问题,有以下一些解决方法:

  • 引入中间层记录消费:在值的外面包装一层,新增一个标记位标记是否被消费过,这样就完全去除了 LiveData 的粘性特性。

    // 一次性值
    open class OneShotValue<out T>(private val value: T) {
    // 值是否被消费
    private var handled = false
    // 获取值,如果值未被处理则返回,否则返回空
    fun getValue(): T? {
    return if (handled) {
    null
    } else {
    handled = true
    value
    }
    }
    // 获取上次被处理的值
    fun peekValue(): T = value
    }

  • 带有最新版本号的观察者:LiveData 数据分发给观察者会进行三次判断,其中一次就是数据版本号的判断,如果观察者在被创建添加时其数据版本号不是初始值-1,而是最新的数据版本号,则不会接收到老的数据。从外部修改观察者的数据版本号需要通过反射修改。

  • 使用 Kotlin Flow:SharedFlow 的构造参数中有一个参数 replay,表示重新发射给新的订阅者的数据的数量,可以将旧的数据回播给新的订阅者,默认为0,也就是说不会保留最后发送的数据,只会发送新状态更新,设置为1,效果同 StateFlow 一样,会保留最后发送的数据,也和 LiveData 一样,变得具有粘性。

相关推荐
黑客-秋凌27 分钟前
[CTF/网络安全] 攻防世界 simple_php 解题详析
android·web安全·php
小可的科研日常35 分钟前
快速增加ppt撤回次数的方法
学习
长安不及十里2 小时前
操作日志设计(一) Binlog 方案(Canal+Mq)
分布式·后端·学习·云原生
秦明月132 小时前
【原创学习笔记】实际调试遇到的问题01
笔记·学习
bst@微胖子2 小时前
Python实现接口签名调用
android·java·python
加酶洗衣粉3 小时前
PostgreSQL学习笔记(一):PostgreSQL介绍和安装
笔记·学习·postgresql
青年夏日科技工作者3 小时前
UE5.3 虚幻引擎 安卓Android地图插件开发打包
android·ue4
人工智能技术咨询.3 小时前
工信部电子标准院计算机视觉证书报考指南!
人工智能·深度学习·学习·计算机视觉·语言模型
ThreeYear_s4 小时前
OFDM学习-(二)长短序列和PPDU整体数据处理流程
学习
JavaPub-rodert4 小时前
项目48:简易语言学习助手【源代码】 --- 《跟着小王学Python·新手》
服务器·开发语言·python·学习·microsoft