Jetpack LiveData

166 阅读7分钟

1. 基本使用

官方文档:LiveData - Android 开发者

LiveData 同 Lifecycle 一样,基于观察者模式构建及使用,官方推荐和 ViewModel 配合使用,简单范例如下

public class LiveDataViewModel extends ViewModel {

    private MutableLiveData<String> stringMutableLiveData;
    public MutableLiveData<String> getStringMutableLiveData() {
        if (stringMutableLiveData == null) {
            stringMutableLiveData = new MutableLiveData<>();
        }
        return stringMutableLiveData;
    }
}

public class LiveDataActivity extends AppCompatActivity {

    private LiveDataViewModel model;
    // LiveData测试展示View
    private TextView tvContent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_live_data);

        tvContent = findViewById(R.id.tv_content);
        model = ViewModelProviders.of(this).get(LiveDataViewModel.class);
        // 订阅
        model.getStringMutableLiveData().observe(this, observer);
    }    

    // LiveData 测试事件观察者
    private Observer<String> observer = new Observer<String>() {
        @Override
        public void onChanged(String s) {
            if (tvContent != null) {
                tvContent.setText(s);
            }
        }
    };

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_live_data:
                model.getStringMutableLiveData().postValue(String.format("测试LiveData : %s", System.currentTimeMillis()));
            break;    
        }
    }
} 

上述代码做了这样一件事情:

  1. 创建一个包含了一个 TextView 和 一个 Button 的 activity
  2. 创建 LiveData ,并将 LiveData 与当前 activity 绑定起来
  3. 点击按钮: 获取当前时间戳,通过 LiveData 发送给观察者
  4. 观察者收到回调,将这个时间戳更新到 TextView 上

运行效果如下:
livedata.gif

主要步骤拆解:

  1. ViewModel 内定义 LiveData 实例
    • LiveData 为抽象类,具体实现为 MutableLiveData<T>
  2. 通过 LiveData.observer(FragmentActivity activity, Observer oserver) 将LiveData 和当前 activity 绑定起来:
    • 这样做是为了感知 activity 的生命周期,当 activity 消亡后,不再接收数据更新
    • ViewModel 可以通过 ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass) 初始化

2. 原理分析

2.1 更新数据

更新数据主要分为两种:

  1. 子线程: LiveData.postValue(T value)
  2. 主线程: LiveData.setValue(T value)

其中子线程通过 postValue() 的方式更新数据时,只是通过 Handler 进行了一层包装,切换到了主线程,最终调用的还是通过调用 setValue() 完成的更新。

2.1.1 流程分析

第一步: 看下 setValue() 做了什么

private int mVersion;
private volatile Object mData;

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

这里主要出现有两个关键点:

  • 两个变量
    • mVersion LiveData 用于记录数据更新的版本号, 每次通过 setValue() 更新数据就会加1
    • mData LiveData 内成员,保存了最新一次 setValue() 时更新数据的副本
  • 调用 dispatchingValue(null) 为具体的数据更新逻辑,(注意这里传入的实参为 null

第二步: 看下dispatchingValue() 实现

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        // 因为传入的是 null 所以不会执行这个 if
        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;
}

主要有几个点:

  • 两个变量mDispatchingValuemDispatchInvalidated
    • 两个变量都是为了递归调用的case, 如:当上述 observer 的 onChanged() 被回调时,内部又调用了 LiveData.setValue() 来更新数据,这时正常来说因为方法的递归调用会导致无限循环,而这两个变量就是为了防止这种情况而设置,具体思想和 Lifecycle 中对递归调用的防止思想基本类似,详细可以参考这篇文章:Android架构组件(2)LifecycleRegistry 源码分析
  • 因为传入的是 null , 所以执行 else 分支的逻辑
  • 遍历 mObservers 通知所有观察者:数据更新啦~,新的数据是XX~~~~
    • 这里的 mObservers 为一个 Map 结构,保存了 LiveData.observer(FragmentActivity activity, Observer oserver)时所有的观察者
    • 具体更新方法封装在了 considerNotify()

第三步: 看下 considerNotify() 实现

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }

    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

这里的点:

  • 做了两件事:
    • 通过最后一行代码,调用 onChanged() 将最新数据同步给了观察者
    • 将 observer.mLastVersion 更新为了 mVersion
  • 前边还有三个 if 判断用于异常拦截
    • 前两个表示当前 activity 是否处于可见状态 (onResume被调用,但是 onPause 未被调用的交互态),也是为什么当前 actvity 不可见时, LiveData的更新不会被感知到
    • 第三个 if 稍后分析

这里出现了两个 版本号, observer.mLastVersionLiveData.mVersion, 要弄清楚,需要结合订阅代码,回到订阅处:

使用 LiveData 时的代码:
model.getStringMutableLiveData().observe(this, observer);  

public abstract class LiveData<T> {
    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        // 将观察者进行了一次包装
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        // 保存观察者
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        // 如果已经订阅过了,无需再订阅 lifecycle, 用于递归调用时case的处理
        if (existing != null) {
            return;
        }
        
        // 用包装后的 wrapper ,订阅了 activity 生命周期变化的事件
        owner.getLifecycle().addObserver(wrapper);
    }
} 


class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    // ...省略代码
}

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

几个点:

  • 传入的 observer 就是 Activity 中我们用于观察数据变化的观察者,这里又被包装成了 LifecycleBoundObserver
    • LifecycleBoundObserver 继承了 ObserverWrapper, 所以也继承了 mLastVersion,所以上边的 observer.mLastVersion 就是 LifecycleBoundObserver.mLastVersion
    • 因为可以有很多观察者,所以这里同 Lifecycle 一样,使用 Map 保存,变量就是这里的 mObservers
  • 最后使用这个包装 LifecycleBoundObserver 对象订阅了当前 Activity 上的 Lifecycle 变化事件
    • 这也同时解释了为什么要包装成LifecycleBoundObserver , 因为 Lifecycle 的订阅者可能会通过 APT生成,通过反射等诸多不同方式调用,所以包装一层,统一面对 LifecycleEventObserver 接口调用,更加方便管理,具体参见 Lifecycle 的实现原理
    • 也是 LiveData 能够感知 Activity 生命周期变化的又一次证明

回到最初的问题上,结合这里的逻辑,梳理可得:

  • 可能有多个观察者观察 LiveData 上的数据变化,所以是 LiveData 这个被观察者和诸多观察者的一对多的关系
  • 两个版本号:
    • LiveData 上的 mVersion 用于记录当前 LiveData 上数据更新的版本/次数
    • 诸多 Observer 上各自维护一个自己 mLastVersion 用于表示当前观察者收到的数据的版本
    • 每次 LiveData 将数据通知到一个观察者上时,将其 mLastVersion 更新为自己的 mVersion 的值

LiveData 和其诸多观察者的关系体现为:

image.png

结合上述代码,再将 LiveData 和 Activity 的关系引入后,将是:

image.png

3. 粘性事件

LiveData 的正常使用步骤为:

  1. 观察者订阅 LiveData 数据更新事件
  2. LiveData 通知订阅者数据更新了
  3. 观察者收到了更新的数据

但是在使用过程中发现, 即使 步骤1步骤2 顺序反了,观察者最终还是会收到数据更新的结果, 这种情况称为 LiveData 的粘性事件

PS: 但是有个特殊情况需要关注 如果多次调用 setValue() 方法,进行数据更新,之后再使用观察者订阅LiveData的数据变化,在产生订阅关系后,只能收到最后一次 setValue() 的值。


3.1 常规粘性事件

很简单,先回到 dispatchingValue 方法上

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            considerNotify(initiator);
            initiator = null;
        } else {
            // 还没订阅,所以 mObservers 中取不到相应观察者
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}

因为还没有通过 observe 产生订阅关系,所以在遍历过程中拿不到相应的观察者,所以就会一直在这里空转,但是一旦订阅后,这里的 mObservers 可以拿到相应 observer了,就可以直接通过 considerNotify() 通知观察者更新数据了 然后再回到 considerNotify()

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }

    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.((T) mData);
}

很显然

  • 首先前两个 if 肯定没有命中,没啥可说的
  • 又因为当前 observer 为初次订阅,所以 mLastVersion 一定为初始值,查源码得知为 -1
  • 又因为 mVersion 初始值为 0, 且 setValue() 每次调用都会执行 mVersion++, 所以这里 observer.mLastVersion >= mVersion 也一定不成立
  • 这样,事件最终就通过最后的 onChanged更新到观察者那里了,也就产生了 粘性事件

3.2 为什么多次只能收到一次更新

那么多次 setValue() 再订阅后,为什么只能收到最后一次更新呢? 也很简单,LiveData 内用的 mData 记录数据,每次都会被覆盖罢了

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    // 每次都会覆盖上一次的值
    mData = value;
    dispatchingValue(null);
}

3.3 粘性事件的解决办法

参考:关于LiveData粘性事件所带来问题的解决方案