Android技术知识Android开发经验谈Android开发

Jetpack之LiveData组件解析

2022-02-21  本文已影响0人  愿天堂没Android

一、为什么使用LiveData

在Android开发之初,大部分代码都放在一个Activity中,导致Activity臃肿且难以单元测试。后来,诞生了MVCMVPMVVM等开发架构,通过分层抽离Activity中的代码。这种分层架构模式可以将逻辑从View中分离出来,但是无法感知Activity的生命周期,Activity 的生命周期必须通知这些组件。

LiveData是一种可观察的数据存储器类,具有生命周期感知能力,并可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者,这可以很好的解决上述的相关问题。

如果观察者的生命周期处于STARTEDRESUMED状态,则LiveData会认为观察者处于活跃状态。LiveData通知观察者时会检查其实时状态,仅会通知处于活跃状态的观察者。一个观察者处于 PAUSEDDESTROYED 状态,它将不会收到通知。一旦观察者重新恢复 RESUMED 状态,它将会重新收到 LiveData 的最新数据。

LiveData可以注册和实现LifecycleOwner接口的对象配对的观察者,当相应的Lifecycle对象状态变为DESTROYED,便可移除此观察者,这样就可以防止内存泄漏问题(当 ActivityFragment 的生命周期被销毁时,系统会立即退订它们)

LiveData的特点

  1. 基于观察者模式,是一种持有可被观察数据的类。其需要一个观察者对象,一般是Observer类的具体实现。
  2. 可以感知生命周期,在生命周期活跃时更新组件。其中STARTEDRESUMED就是活跃状态
  3. 可以在生命周期结束时立刻解除对数据的订阅,从而避免内存泄漏等问题
  4. 可以解决Configuration Change问题,配置更改而重新创建Activity或Fragment,其会立即接收最新的可用数据。

二、基本使用

依赖引入

dependencies {
    def lifecycle_version = "xxx"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

    // Annotation processor
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
}

LiveData对象一般使用步骤:

  1. 创建LiveData实例,指定源数据类型,通常在ViewModel中完成
  2. 创建Observer实例对象,实现onChanged()方法,用于接收源数据变化并刷新UI,通常在界面控制器(ActivityFragment)中创建
  3. LiveData实例使用observer()方法添加观察者,并传入LifecycleOwner
  4. LiveData实例使用setValue()/postValue()更新源数据(子线程要postValue

2.1 简单使用

简单演示LiveData的使用

Activity中创建LiveData对象

LiveData是一个抽象类,不能直接使用,通常用它的直接子类MutableLiveData来实现

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val mutableLiveData = MutableLiveData<String>() //指定源数据类型为String
        mutableLiveData.observe(this) {
            Log.d(TAG,"onChanged:$it" )
            Toast.makeText(this,it,Toast.LENGTH_LONG).show()
        }
        mutableLiveData.postValue("Hello LiveData") //更新源数据
    }
}

observer方法有两个参数:

  1. 参数一:LifecycleOwner,前述代码为MainActivity本身
  2. 参数二:Observer<T>,前述代码新建了一个Observer<String>,在onChange方法中回调

大多数情况,LiveDataobserve方法放在onCreate中,如果放在onResume中会出现多次调用的情况。前述情况下,onStart、onResume、onPause为活跃状态,onchanged会立即回调

LiveData还提供了一个observeForever()方法,与observe()的区别在于当LiveData包装的数据发生变化时,无论页面处于什么状态,observeForever()都能收到通知。使用完成后,要记得调用removeObserver()来停止对LiveData的观察,否则LiveData会一直处于激活状态,Activity永远不会被系统自动回收。

ViewModel中创建LiveData对象

实际开发中一般建议LiveData对象是存储在ViewModel对象中,主要原因有:

  1. 避免 ActivityFragment 过于庞大,让其专注于数据显示而不负责存储数据
  2. LiveData实例与特定的ActivityFragment 实例分离开,保证LiveData对象在配置更改后继续存在
class NameViewModel : ViewModel() {

    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }
}

LiveData对象观察

建议在组件的onCreate() 方法开始观察LiveData对象,因为

  1. 确保系统不会从 ActivityFragmentonResume() 方法进行冗余调用
  2. 确保 ActivityFragment 变为活跃状态后具有可以立即显示的数据

通常,LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。

如果观察者从非活跃状态更改为活跃状态时也会收到更新。如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。

class NameActivity : AppCompatActivity() {

    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ......
        val nameObserver = Observer<String> { newName ->
            nameTextView.text = newName
        }
        model.currentName.observe(this, nameObserver)
    }
}

在传递 nameObserver 参数的情况下调用 observe()) 后,系统会立即调用 onChanged(),从而提供 mCurrentName 中存储的最新值。

如果 LiveData 对象尚未在 mCurrentName 中设置值,则不会调用 onChanged()

2.2 扩展LiveData

关于LiveData,可以根据实际需要自定义扩展,复写onActive()onInactive()回调方法即可。

首先看一下官方提供的一个示例

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price   //监听到股价变化,
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }
}

至此,你可以使用StockLiveData

public class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val myPriceListener: LiveData<BigDecimal> = ...
        myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })
    }
}

LiveData 对象具有生命周期感知能力,这一事实意味着您可以在多个 ActivityFragmentService 之间共享这些对象。你可以将LiveData类实现为一个单例

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager: StockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }

    companion object {
        private lateinit var sInstance: StockLiveData

        @MainThread
        fun get(symbol: String): StockLiveData {
            sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
            return sInstance
        }
    }
}

class MyFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })
    }

只有当 存在活跃的观察者(LifecycleOwner)时 才会连接到 股价更新服务 监听股价变化

多个 FragmentActivity 可以观察 MyPriceListener 实例。仅当一个或多项系统服务可见且处于活跃状态时,LiveData 才会连接到该服务。

2.3 转换LiveData

如果想要在LiveData对象分发给观察者之前对其中存储的值进行更改,或者您可能需要根据另一个实例的值返回不同的 LiveData 实例。可以使用 Transformations 类,该类包括可应对这些情况的辅助程序方法。

Transformations.map()

该方法对存储在 LiveData 对象中的值应用函数,并将结果传播到下游。

val mutableLiveData = MutableLiveData<String>()
mutableLiveData.observe(this) {
    Log.d(TAG, "onChanged:$it")
    Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
//LiveData返回值实例转换
val transformedLiveData = Transformations.map(mutableLiveData) {name ->
                                                                "${name}LiveData is great"
                                                               }
transformedLiveData.observe(this){
    Log.d(TAG,"onChange2$it")
}
mutableLiveData.postValue("Hello LiveData ")

/**打印结果**/
onChanged:Hello LiveData
onChange2Hello LiveData LiveData is great

Transformations.switchMap()

map()类似,对存储在LiveData对象中的值应用函数,并将结果解封和分派到下游。传递给 switchMap() 的函数必须返回 LiveData 对象。

private lateinit var mutableLiveData1:MutableLiveData<String>
private lateinit var mutableLiveData2:MutableLiveData<String>
private lateinit var liveDataSwitch:MutableLiveData<Boolean>

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    mutableLiveData1 = MutableLiveData()
    mutableLiveData2 = MutableLiveData()
    liveDataSwitch = MutableLiveData()
    val transformedLiveData = Transformations.switchMap(liveDataSwitch,
        Function<Boolean?, LiveData<String>> {
            return@Function if (it) mutableLiveData1 else mutableLiveData2
        })
    transformedLiveData.observe(this){
        Log.d(TAG,"onChanged:$it")
    }
    liveDataSwitch.postValue(false)
    mutableLiveData1.postValue("LiveData init")
    mutableLiveData2.postValue("LiveData developer")
}

新建一个MutableLiveData<Boolean>来控制切换并赋值给liveDataSWitch。当liveDataSwitch的值为true时返回mutableLiveData1,否则返回mutableLiveData2。通过这种方式,达到切换监听的目的。

2.4 合并多个LiveData源

MediatorLiveData继承自mutableLiveData,它可以将多个LiveData数据源集合起来,可以达到一个组件监听多个LiveData数据变化的目的

private lateinit var mutableLiveData1:MutableLiveData<String>
private lateinit var mutableLiveData2:MutableLiveData<String>
private lateinit var liveDataManager:MediatorLiveData<String>
private lateinit var text:TextView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    text = findViewById(R.id.tv_text)
    mutableLiveData1 = MutableLiveData()
    mutableLiveData2 = MutableLiveData()
    liveDataManager = MediatorLiveData()
    liveDataManager.addSource(mutableLiveData1){
        Log.d(TAG,"onChange1:$it")
    }

    liveDataManager.addSource(mutableLiveData2){
        Log.d(TAG,"onChange2:$it")
    }
    liveDataManager.observe(this){
        Log.d(TAG,"onChanged:$it")
    }
    mutableLiveData1.postValue("LiveData init")
    mutableLiveData2.postValue("LiveData developer")

    text.setOnClickListener(){
        mutableLiveData1.postValue("change LiveData1 data")
    }
}

/**打印结果**/
onChange1:LiveData init
onChange2:LiveData developer
onChange1:change LiveData1 data

通过MediatorLiveDataaddSource将两个MutableLiveData合并到一起,这样当任何一个MutableLiveData数据发生变化时,MediatorLiveData都可以感知到

三、原理解析

这里是基于2.4.0版本进行原理分析。

在学习LiveData的实现原理前,先了解一下几个核心角色:

看一下LiveData的原理实现机制图,如下图所示

img img

LiveData的实现原理,简单总结为

  1. LiveData的实现主要可以分为添加观察者、事件回调和事件更新三部分
  2. 添加观察者:将LifecycleOwnerobserver进行包装成可感知生命周期的包装类,添加到LifecycleObserver中,它将在LifecycleOwner更改状态时得到通知
  3. 事件回调:收到LifecycleOwner状态更改通知时,进行状态判断等处理,然后通知之前添加的观察者对象
  4. 事件更新:更新数据版本号,遍历观察者对象,进行事件分发通知

LiveData其他补充知识

  1. LiveData的观察者只能与一个LifecycleOwner绑定, 否则会抛出异常。而一个 owner 可以绑定多个 Observer 实例
  2. 使用observeForever()方法,意味着给定的观察者将接收所有事件,并且永远不会被自动删除,不管在什么状态下都能接收到数据的更改通知,使用完注意手动移除
  3. LiveData利用数据版本管理方法,确保只会发送最新的数据,但是要注意数据倒灌

3.1 添加观察者

LiveData通过observer()方法来注册观察者,从该方法进行入手

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    //当前绑定的组件(activity或fragment)状态为为DESTROYED的时候,则会忽视当前的订阅请求
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    //创建生命周期感知的观察者包装类
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    //如果指定的键尚未与某个值关联,则将其与给定的值关联
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    //对应观察者只能与一个owner绑定
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                                           + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    //添加一个LifecycleObserver,它将在LifecycleOwner更改状态时得到通知
    owner.getLifecycle().addObserver(wrapper);
}

LiveDataobserver()的主要内容为:

  1. 首先判断LifecycleOwner是否为DESTROYED状态,如果是就直接忽略,不能添加。
  2. 其次用LifecycleOwnerobserver 组装成LifecycleBoundObserver包装实例wrapper
  3. 使用putIfAbsent()方法observer-wrapper作为key-value添加到观察者列表mObservers中。不能添加具有不同生命周期的相同观察者,否则会抛异常,但是同owner可以add多个Observer
  4. 最后用LifecycleOwnerLifecycle添加observer的封装wrapper

3.2 事件回调

LifecycleBoundObserver

LiveData通过observe()方法添加了LifecycleBoundObserver,看一下内部实现

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() {
        //至少是STARED状态
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    //关键代码
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
                               @NonNull Lifecycle.Event event) {
        //LifecycleOwner变成DESTROYED状态,则移除观察者
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        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类主要内容是:

  1. 对原始Observer进行包装,把LifecycleOwnerObserver绑定在一起。当LifecycleOwner处于活跃状态, LifecycleBoundObserver就是活跃的观察者。重写了shouldBeActive()onStateChanged()方法。从Lifecycle中可知,其实现了LifecycleEventObserver接口。
  2. LifecycleOwner生命周期状态变化时检查,如果是DESTROYED状态,则移除观察者。
  3. 如果不是DESTROYED状态,将调用父类ObserverWrapperactiveStateChanged()方法处理 这个生命周期状态变化
  4. shouldBeActive()的值作为参数,至少是STARTED状态为true,即活跃状态为true

ObserverWrapper

LifecycleBoundObserver继承了ObserverWrapper,看一下其实现

private abstract class ObserverWrapper {
    void activeStateChanged(boolean newActive) {
        if (newActive == mActive) {
            return;     //活跃状态未发生改变,不会处理
        }
        mActive = newActive;
        changeActiveCounter(mActive ? 1 : -1);
        if (mActive) {
            dispatchingValue(this); //观察者变为活跃,进行数据分发
        }
    }
    ......
}

@MainThread
void changeActiveCounter(int change) {
    int previousActiveCount = mActiveCount;
    mActiveCount += change;
    if (mChangingActiveState) {
        return;     //如果正在变更 则返回
    }
    mChangingActiveState = true;
    try {
        while (previousActiveCount != mActiveCount) {
            boolean needToCallActive = previousActiveCount == 0 && mActiveCount > 0;
            boolean needToCallInactive = previousActiveCount > 0 && mActiveCount == 0;
            previousActiveCount = mActiveCount;
            if (needToCallActive) {
                onActive(); //活跃的观察者数量 由0变为1。这是个空函数,根据需要重写
            } else if (needToCallInactive) {
                onInactive();   //活跃的观察者数量 由1变为0。空函数,根据需要重写
            }
        }
    } finally {
        mChangingActiveState = false;
    }
}

LifecycleOwner状态发生改变时,会调用其activeStateChanged()方法,根据活跃观察者数量的变化,分别调用onActive()onInactive()

如果状态为Active,会调用dispatchingValue方法,并将自身传进去。

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) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    //标记分发结束
    mDispatchingValue = false;
}

dispatchingValue()主要内容是:

  1. 标记事件分发状态,如果正处于分发状态,表示分发无效直接返回
  2. observerWrapper不为空,就使用considerNotify()通知真正的观察者,observerWrapper为空 则遍历通知所有的观察者。

看一下considerNotify()方法实现

private void considerNotify(ObserverWrapper observer) {
    //观察者非活跃,直接return
    if (!observer.mActive) {
        return;
    }
   
    //观察者状态活跃,但是当前变为了不可见状态,再调用activeStateChanged方法,并传入false,其内部会再次判断
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    //如果数据版本已经是最新的了,那么直接返回
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    //修改数据版本
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);//回调真正的mObserver的onChanged方法
}

considerNotify()方法主要内容:

  1. 如果ObserverWrappermActive值不为true,就直接return
  2. 如果当前observer对应组件的状态不是Active,就会再次调用activeStateChanged方法,并传入false,其方法内部会再次判断是否执行onActive方法和onInactive方法回调。
  3. 如果判断条件都满足会调用ObserveronChanged方法,这个方法正是使用LiveDataobserve方法的回调。

considerNotify()方法中也涉及到了相应的观察者数据版本号的比较问题

  1. 当页面从不可见变为可见,将LiveData中的数据版本号跟对应的观察者中的版本号进行比较,如果大于,则调用onChanged()进行数据的回调。
  2. 如果页面为不可见,那么不会进行数据的回调处理。

所以LiveData注册观察者后的流程为:

  1. 调用 observe() 注册后,绑定了LifecycleOwner,在active状态下,使用LiveDatasetValue发送数据,则 Observer 会立马接受到该数据修改的通知
  2. 状态变更后的触发流程大致为:observe ——> onStateChanged ——> activeStateChanged ——> dispatchingValue ——> considerNotify ——> onChanged
  3. onChanged方法,交给外部开发者处理接收消息事件的逻辑

3.3 事件更新

Activity中,通过postValue或者setValue改变LiveData的数据,在通过Observer,观察LiveData数据的变化。那么LiveData的数据更新是如何通知到Observer的?。

setValue逻辑

LiveData 更新数据方式有两个,一个是 setValue(), 另一个是 postValue()postValue() 在内部会抛到主线程去执行更新数据,因此适合在子线程中使用;而 setValue() 则是直接更新数据。

看一下setValue()方法内部实现

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");   //检查是否在主线程
    mVersion++; //默认值是-1,每次更新数据都会自增
    mData = value;  //更新的数据赋值给mData
    dispatchingValue(null); //调用 dispatchingValue()方法并传入null,将数据分发给所有观察者。dispatchingValue如果传入null则是所有的观察者,如果是具体的ObserverWrapper对象,则通知到具体的Observer。
}
  1. setValue()方法会将数据版本号+1,并进行数据分发
  2. 调用dispatchingValue() 进行事件分发。如果参数为null且为active状态,那么会遍历所有的监听者,逐个通知所有观察者进行了数据的变化

所以setValue后的触发流程为:setValue ——> dispatchingValue(null) ——> considerNotify——> onChanged

postValue逻辑

看一下postValue()实现

protected void postValue(T value) {
    boolean postTask;   //用于判断是否要更新
    //加锁解决多个子线程同时调用问题
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;   //将数据存入 mPendingData
    }
    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()是将消息通过线程池分发到主线程去处理。

四、常见问题

4.1 数据倒灌

使用LiveData的一个常见问题是数据倒灌,表现为用户在页面没有做任何操作,却进行了页面跳转,数据请求等操作。

LiveData的设计原则:在页面重建时,LiveData自动推送最后一次数据,不必重新去向后台请求

对于页面重建,常见的场景有:

  1. 屏幕旋转
  2. 系统语言切换
  3. 内存不足,应用在后台被系统杀死。之后用户再重新进入应用
  4. ......

这里我们讨论的数据倒灌跟前两种场景相关。

LiveData实例创建建议在ViewModel中,而资源配置变更并不会导致ViewModel实例销毁。

通过一个示例演示一下数据倒灌问题

//MainActivity.kt
private fun initObserver() {
    mViewModel.testLiveData.observer(this) {
        Log.i("MainActivity", "testLiveData value == $it")
        Thread{
            SystemClock.sleep(3000)
            startActivity<SecondActivity>()
        }.start()
    }
}
private fun onClick(){
    mBinding.btnTest.setOnClickListener { mViewModel.testLiveData.value = 3 }
}

//MainViewModel.kt
val testLiveData = MutableLiveData<Int>()

上述代码示例,如果用户在MainActivity中点击按钮,会将ViewModel中的testLiveData的值置为3,然后延迟3S后会跳转到下一个页面。如果用户返回当前页面,在当前页面旋转屏幕,发现会自动跳转到下一个页面。这就是数据倒灌导致的。

前述页面旋转重建了,会自动推送最后一次数据。LiveData的事件回调过程是:observe ——> onStateChanged ——> activeStateChanged ——> dispatchingValue ——> considerNotify ——> onChanged。通过断点调试,可将问题定位在considerNotify

private void considerNotify(ObserverWrapper observer) {
    //观察者非活跃,直接return
    if (!observer.mActive) {
        return;
    }

    //观察者状态活跃,但是当前变为了不可见状态,再调用activeStateChanged方法,并传入false,其内部会再次判断
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    //此处判断无效导致数据倒灌
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    //修改数据版本
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);//回调真正的mObserver的onChanged方法
}

LiveData在前述屏幕旋转中也执行了事件分发,其原因是if (observer.mLastVersion >= mVersion) 没有生效。

mVersion认识

// LiveData.java   
static final int START_VERSION = -1;
private int mVersion;

mVersionLiveData的成员变量,一个LiveData维护一份实例对象

public LiveData() {
    mData = NOT_SET;
    mVersion = START_VERSION;
}

在初始化LiveData()时,mVersion设置为-1,之后每次调用setValue等方法,会执行mVersion++自增方法。

mLastVersion认识

mLastVersion默认值也是-1。如果分发事件成功,将当前LiveDatamVersion赋值给mLastVersion

private abstract class ObserverWrapper {
    final Observer<? super T> mObserver;
    boolean mActive;
    // 第一处
    int mLastVersion = START_VERSION;
}
private void considerNotify(ObserverWrapper observer) {
    ...
        // 第二处
        if (observer.mLastVersion >= mVersion) {
            return;
        }
    // 第三处
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

屏幕旋转后,该值重新变为-1,导致了数据倒灌问题发生。

回看一下LiveDataobserver()方法

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    
    ......
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ......
}
  1. Activity等页面重建后,LiveData调用observe(),方法内会new一个新的LifecycleBoundObserver对象,该对象继承ObserverWrapper
  2. ObserverWrapper类初始化会重新初始化 int mLastVersion = START_VERSION;mLastVersion赋值为-1
  3. 因为observer.mLasterVersion < mVersion ,considerNotify()方法中的判断失效,重新分发事件,导致数据倒灌

页面重建后,会自动推送最后一次数据。为什么页面重建没有手动调用setValue()等方法,也会触发事件分发considerNotify()方法?

considerNotify()方法在内部做了多重判断,如果如果当前observer对应组件的状态不是Active,就会再次调用activeStateChanged方法,并传入false,其方法内部会再次判断是否执行onActive方法和onInactive方法回调。

considerNotify()` ->`dispatchingValue()` -> `activeStateChanged()` -> `onStateChanged()
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
                           @NonNull Lifecycle.Event event) {
    //如果当前Activity的状态是onDestory,移除
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
    if (currentState == DESTROYED) {
        removeObserver(mObserver);
        return;
    }
    Lifecycle.State prevState = null;
    // 上一次的state跟当前的不同时,执行事件分发
    while (prevState != currentState) {
        prevState = currentState;
        activeStateChanged(shouldBeActive());
        currentState = mOwner.getLifecycle().getCurrentState();
    }
}

如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。

简单小结

  1. 页面异常销毁重建,ViewModel会保存销毁之前的数据,在Activity重建完成后进行数据回复,所以LiveData成员变量中的mVersion会恢复到重建之前的值
  2. 页面重建后会调用LiveDataobserve()方法,方法内部会重新new一个实例,会将mLastVersion恢复到初始值。
  3. 由于LiveData本身的特性,Activity的生命周期由非活跃变成活跃时,LiveData会触发事件分发,导致屏幕旋转或者切换系统语言后出现数据倒灌

数据倒灌解决方法

4.2 postValue数据丢失

postValue数据丢失的典型表现就是使用LiveData时,连续postValue两次,发现第一次的值会丢失。

viewModel.testLiveData1.postValue("hello click")
viewModel.testLiveData1.postValue("hello kotlin")

/**实际只会打印第二次postValue的值**/
testLiveData1 value == hello kotlin

看一下postValue的内部实现

volatile Object mPendingData = NOT_SET;

/**
* If you called this method multiple times before a main thread executed a posted task, only
* the last value would be dispatched.
*/
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;   //暂存数据,后面的数据会覆盖前面的
    }
    if (!postTask) {    //保证只抛一个mPostValueRunnabl
        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方法的注释上也说明了这一情况。

postValue的主要内容:

  1. 每次调用postVaule,会将新值赋给mPendingData
  2. Runnable中进行值的分发,通过ArchTaskExecutor将任务发布到主线程中

到这其实连续postValue时值会丢失的原因已经清楚了

  1. 调用postValue时,其实只是将值暂存到mPendingData,然后往主线程抛一个Runnable,通过setValue将暂存的值设置进去,回调观察者
  2. 如果在这个Runnable真正执行前多次postValue,只会改变暂存值mPendingData,通过postTask的检测不会再往主线程抛Runnable

作者:者文
链接:https://juejin.cn/post/7065693389554974728
如有侵权,请联系删除!

上一篇下一篇

猜你喜欢

热点阅读