Jetpck之LiveData解析

1. LiveData简介与使用

1.1 简介

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activity、fragment 或 service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

1.2 简单使用

  • 通常LiveData都是作为ViewModel层存在于ViewModel中
kotlin 复制代码
class NameViewModel : ViewModel() {

    private val _currentName: MutableLiveData<String> = MutableLiveData()

    // 只暴露不可变的LiveData的目的是防止外界变更,使得ViewModel才是唯一可信来源
    val currentName: LiveData<String> = _currentName

    fun fetchUserName() {
        viewModelScope.launch {
            delay(2000)
            _currentName.value = "BTPJ"
        }
    }
}
kotlin 复制代码
class NameActivity : AppCompatActivity() {
    /** 使用activity-ktx依赖中的代理直接赋值 */
    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_name)

        // LiveData监听
        model.currentName.observe(this) {
            findViewById<TextView>(R.id.tv_name).text = it
        }
    }
}
  • 更多关于LiveData的扩展转换以及合并可直接查看官方教程,这里主要分析LiveData的源码

2. 源码分析

2.1 添加观察者

  • LiveData主要是用到了观察者模式,其中添加观察者是通过observe()
java 复制代码
// LiveData.java
/**
 * 添加观察者. 事件在主线程分发. 如果LiveData已经有数据,将直接分发给observer。 
 * 观察者只在LifecycleOwner活跃时才接收事件,如果变为DESTROYED状态,observer自动移除。 
 * 当数据在非活跃时更新,observer不会接收到。变为活跃时 将自动接收前面最新的数据。 
 * 
 * @param owner 观察绑定的LifecycleOwner
 * @param  Observer 要添加的观察者
 */
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    // 限制主线程调用
    assertMainThread("observe");
    
    // 当绑定的LifecycleOwner为DESTROYED状态时直接忽略
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        return;
    }
    
    // 将owner和observer一起包装成LifecycleBoundObserver对象
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    // 将observer作为key,wrapper作为value存入Map中,putIfAbsent表示当前是否存在该  
    // observer,存在就直接返回已存储的ObserverWrapper值,不存在就直接添加
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    // 同一个观察者与不同的LifecycleOwner绑定直接报错
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    // 已存在就不再重复绑定
    if (existing != null) {
        return;
    }
    // 不存在就让wrapper观察owner的生命周期,即绑定生命周期
    owner.getLifecycle().addObserver(wrapper);
}
  • 主要做了两件事
    *
    1. 将观察者缓存进Map
      1. 将观察者的观察周期与LifecycleOwner的生命周期绑定

2.2 事件分发回调

2.2.1 setValue/postValue

  • 先从LiveData设置数据开始着手,postValue是任意线程(主要针对子线程)调用,而setValue则只能是主线程调用
java 复制代码
/** 从其他线程发送数据到主线程 */
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        // 数据缓存至mPendingData
        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) {
            // 取出postValue()缓存的value
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        // 最终还是调用了setValue(),只不过帮你切到了主线程
        setValue((T) newValue);
    }
};
  • 由此可见postValue()本质上还是调用了setValue(),只不过帮你从子线程切到了主线程,简单看下是怎么切的
java 复制代码
// DefaultTaskExecutor.java
@Override
public void postToMainThread(Runnable runnable) {
    if (mMainHandler == null) {
        synchronized (mLock) {
            if (mMainHandler == null) {
                mMainHandler = createAsync(Looper.getMainLooper());
            }
        }
    }
    mMainHandler.post(runnable);
}
  • 没有其他办法,果然还是通过Handler
  • 接着看下setValue()
java 复制代码
@MainThread
protected void setValue(T value) {
    // 限制只能在主线程调用
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    // 关键事件分发
    dispatchingValue(null);
}
  • 接着看分发事件的方法dispatchingValue(null),注意传递的参数为null
java 复制代码
/** 分发事件 */
void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            considerNotify(initiator);
            initiator = null;
        } else {
           // setValue传值为null走的这里
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}
  • 可以看到最终会遍历所有的观察者,并逐个调用considerNotify(observer)
java 复制代码
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    if (!observer.shouldBeActive()) {
       // 若当前observer对应owner非活跃,就会调用activeStateChanged方法,
       // 并传入false,其内部会再次判断
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    // 这里就是关键的onChanged(mData)回调,会在活跃状态执行
    observer.mObserver.onChanged((T) mData);
}

/** 判断是否是活跃状态(RESUMED状态) */
@Override
boolean shouldBeActive() {
    return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}

void activeStateChanged(boolean newActive) {
    if (newActive == mActive) {
        return;
    }
    
    mActive = newActive;
    changeActiveCounter(mActive ? 1 : -1);
    if (mActive) { 
        dispatchingValue(this);
    }
}
  • 整个过程就是一个典型的观察者模式,只是其中融入了对LifecycleOwner是否活跃的判断,只有在活跃时才回调。

2.2.3 粘性事件

  • 粘性事件即发送消息事件早于注册事件,依然能够接收到消息通知
  • LiveData默认就支持粘性事件(而且没有关闭开关),简单分析下支持的原理,主要切入点还是所谓后注册的注册事件observe()
java 复制代码
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
   ...
   LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
   ...
   owner.getLifecycle().addObserver(wrapper);
}
  • 核心在于owner.getLifecycle().addObserver(wrapper),addObserver()是Lifecycle库用来添加一个LifecycleObserver,当LifecycleOwner改变状态时,它会被通知,通知回调是onStateChanged(...),看下这个wrapper(即LifecycleBoundObserver对象)的onStateChanged()方法
java 复制代码
// LiveData.java
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() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        if (currentState == DESTROYED) {
           // 观察者是DESTROYED状态时移除观察
            removeObserver(mObserver);
            return;
        }
        Lifecycle.State prevState = null;
        while (prevState != currentState) {
            prevState = currentState;
            // 关键代码
            activeStateChanged(shouldBeActive());
            currentState = mOwner.getLifecycle().getCurrentState();
        }
    }
    ...
}
  • 与上面一样最终还是会执行activeStateChanged(),传参为shouldBeActive()即是否处于活跃状态
java 复制代码
void activeStateChanged(boolean newActive) {
    if (newActive == mActive) {
        return;
    }
    
    mActive = newActive;
    changeActiveCounter(mActive ? 1 : -1);
    if (mActive) { 
        // 当判断为活跃状态时即会执行下面分发事件代码
        dispatchingValue(this);
    }
}
  • 最终会执行dispatchingValue(this),流程跟上面一样,只是传参不再是null了,简单再看下里面的逻辑
java 复制代码
void dispatchingValue(@Nullable ObserverWrapper initiator) {
    ...
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            // 有值会执行这里
            considerNotify(initiator);
            initiator = null;
        } else {
           ...
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}
  • 可见最终还是会执行considerNotify(initiator),只不过不再是循环执行所有观察者,而是只针对那一个观察者

3、LiveData存在的问题

3.1 数据倒灌

3.1.1 问题描述

  • 所谓数据倒灌,其实就是新添加的监听依然会收到老数据的发送,其实很好理解,就是粘性特性带来的弊端,粘性特性有时候很有用,但有时候又会带来灾难,但LiveData官方并没有提供像EventBus那样取消粘性的Api,所以这个灾难就产生了一个新词即数据倒灌。
  • 举一个简单又常见的例子:ViewModel数据请求时出错了,我们Activity收到错误进行Toast提示,这时当Activity配置改变(如旋转屏幕),这个错误会又提示一遍
kotlin 复制代码
class MainViewModel : BaseViewModel() {

    private val _errorLiveData = MutableLiveData<String>()
    val errorLiveData: LiveData<String> = _errorLiveData

    fun loadData() {
        viewModelScope.launch {
            LogUtil.d("LTP","开始请求")
            delay(1000)
            _errorLiveData.value = "模拟请求出错"
        }
    }
}
kotlin 复制代码
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mViewModel.apply {
            errorLiveData.observe(this@MainActivity) {
                LogUtil.d("LTP", it)
                ToastUtil.showShort(this@MainActivity, it)
            }

            // 避免说是受到多次请求的影响,加个判断只请求一次
            if (errorLiveData.value == null) { 
                loadData()
            }
        }
    }
}

运行结果:
开始请求
模拟请求出错

-----屏幕翻转后----
模拟请求出错
  • 原因很简单,屏幕翻转时,会重新执行Activity的onCreate(),observe()会再次执行,由于粘性事件的原因,那个错误值依然会被接收到

3.1.2 解决方式

  • 所谓处理数据倒灌最典型的思路一种是直接让LiveData取消粘性特性,一种是在自定义LiveData并在onChange()回调中做判定
  1. 使用第三方库UnPeek-LiveData
  2. 使用事件包装器
  3. SingleLiveEvent
  4. 反射干涉Version

3.2 postValue导致的数据丢失

3.2.1 问题描述和产生原因

  • 问题描述: 当使用postValue()发送数据时,生产速度(postValue)超过消费速度(onChange)时,会存在中间数据丢失的问题
  • 产生原因: 回顾下postValue
java 复制代码
/** 从其他线程发送数据到主线程 */
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        // 数据缓存至mPendingData
        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) {
            // 取出postValue()缓存的value
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        // 最终还是调用了setValue(),只不过帮你切到了主线程
        setValue((T) newValue);
    }
};
  • 当发送的旧消息还未及时消费,新消息中的value就会赋值到缓存的mPendingData,再进行消费时取出的newValue就是新的value,从而导致旧的value丢失

3.2.2 解决方式

  • 可以使用Kotlin的Flow(支持缓存背压),或者Rxjava的flowable

3.3 数据抖动

  • 原因:由于LiveData并未对发送的旧值与新值做对比,所以当同一个数据被多次发送时都会被回调执行
  • 解决方案 :自定义LiveData在onChange中判断处理或者将LiveData换成stateFlow

4. 参考文章

相关推荐
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391914 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup
rkmhr_sef14 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb