前言 ViewModel + LiveData 是很推荐和不错的写法, LiveData 是感知生命周期的, 所以对于界面来说, 观察数据变化及接收。 其实说白了就是观察者模式, 并且加上了生命周期限制, 在一定的生命周期中可以回调出去, 其他生命周期不再回调而已。 到底是怎么实现的呢, 我们可以着重分析下。
基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class MainModel : ViewModel () { val strResult = MutableLiveData<String>() fun requestStr () { strResult.value = "v1" strResult.postValue("v2" ) } } mModel.strResult.observe(this ) { strResult-> }
源码分析 基本使用就是这样子拉。 LiveData 的 setValue 是必须在主线程设置数据, 而 postValue 是不限制在什么线程中设置数据, 会自动切换到主线程回调数据。 我们先看看设置数据是怎么处理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MutableLiveData <T > extends LiveData <T > { public MutableLiveData(T value) { super (value); } public MutableLiveData() { super (); } @Override public void postValue(T value) { super .postValue(value); } @Override public void setValue(T value) { super .setValue(value); } }
我们可以很清晰的看到, setValue 和 postValue 调用了父类的 LiveData 的实现。 我们先看看 setValue。
1 2 3 4 5 6 protected void setValue(T value) { assertMainThread("setValue" ); mVersion++; mData = value; dispatchingValue(null ); }
是不是很简单, 检验下是不是主线程, 然后把数据设置到 mData 成员变量上, 然后直接调用分发。 分发的话, 我们稍后一起分析。 我们看下 postValue。
1 2 3 4 5 6 7 8 9 10 11 protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } if (!postTask) { return ; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); }
因为 postValue 可能是在分线程中调用的, 所以在设置数据的时候加了一把锁。 把数据设置到了 mPendingData 成员变量上。 我们可以发现 postTask 变量为 false 的时候可能会直接 return, 侧面我们也要认识到一个问题, 就是 postValue 是会丢数据的。 丢数据的条件就是 mPendingData 是不是等于 NOT_SET, 也就是是不是上一条 postValue 是否处理完了, 如果没有处理完的话, 就不再设置了。 坑不坑你说, 所以使用的时候一定要谨慎谨慎再谨慎。 设置完数据后, 直接调用了 postToMainThread 回调到主线程去运行, 我们直接看 mPostValueRunnable 的实现吧。
1 2 3 4 5 6 7 8 9 10 11 12 private final Runnable mPostValueRunnable = new Runnable() { @SuppressWarnings("unchecked" ) @Override public void run() { Object newValue; synchronized (mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); } };
很简单对吧, 把数据又再次调用 setValue 去了。 我们在这里也观察到 mPendingData 设置了 NOT_SET, 和 postValue 中对于起来了。 如果当前 runnable 还没有处理到这里的话, 那么 mPendingData 就有数值, 那就再来数据就抛弃了。 OK, postValue 还是最后调用了 setValue, 所以我们最后看看怎么分发的吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 ; }
对于 mDispatchingValue 这些变量就不再解释了。 我们看下关键的代码, 不管传入的 ObserverWrapper 参数是否为空, 都是会调用 considerNotify 方法。 在 setValue 会分发的时候明确是传入的 null 的, 所以我们需要关注下 mObservers 变量, 是观察者模式哈。 我们先看看是怎么分发通知的吧。
1 2 3 4 private void considerNotify(ObserverWrapper observer) { observer.mObserver.onChanged((T) mData); }
我们直接看最后一行吧, onChanged 就回调出去了。 我们现在看看我们是怎么往 mObservers 添加我们的观察者的。
1 2 mModel.strResult.observe(this ) { }
肯定是这个没跑了。 我们看看 observe 怎么处理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { assertMainThread("observe" ); if (owner.getLifecycle().getCurrentState() == DESTROYED) { return ; } 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" ); } if (existing != null ) { return ; } owner.getLifecycle().addObserver(wrapper); }
很明显当前线程必须是在主线程中调用的, 其二呢, 如果当前界面或者组件生命周期已经走到尽头了, 那么就不再观察了哈。 我们可以看下下面几个主要的方法。 把 LifecycleOwner 和 Observer 传入构造了一个新的 LifecycleBoundObserver。 从名称也可以看到很明显内部肯定基础了 LifecycleObserver 去关注了生命周期, 从最后一行也能正面因为是 addObserver 了。 我们也终于看到 mObservers 去添加了。 我们现在看看 LifecycleBoundObserver 干了啥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @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) { removeObserver(mObserver); return ; } Lifecycle.State prevState = null ; while (prevState != currentState) { prevState = currentState; activeStateChanged(shouldBeActive()); currentState = mOwner.getLifecycle().getCurrentState(); } } void activeStateChanged(boolean newActive) { if (newActive == mActive) { return ; } mActive = newActive; changeActiveCounter(mActive ? 1 : -1 ); if (mActive) { dispatchingValue(this ); } }
看看这两个方法的实现, 其中 shouldBeActive 是判断当前是否在合理的生命周期, 我们可以看到如果在 STARTED 生命周期后面都可以。 我们看看生命周期发生改变的回调里都做了什么。
如果生命周期到了尽头, 就移出当前监听。
如果生命周期不一致的话, 就会循环去调用 activeStateChanged
我们可以看到在 mActive 为 true 的时候会再次分发当前数据。
通过第 3 点我们可以了解到, 当生命周期第一次进来的时候隔离生命周期的时候, 就会分发一次。 所以在设置回调时会进行分发一次。 如果界面没有走到尽头, 也就是没有运行到 onDestroy 这一步的话, 如果状态再次又 STARTED 之前进来, 且 observer.mLastVersion < mVersion, 那么还会再次回调分发一次。 至于再次回调可能会造成的问题, 我会在最后单独说。 了解到这里的事情, 我们再次看下分发通知的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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); }
前面的 mActive 和 shouldBeActive 又再次的判断了生命周期是否在合理的情况。 只有再合理的情况下的话才会分发。 observer.mLastVersion >= mVersion 这个判断呢, 我们可以看下 mVersion 只有在 setValue 中才会递增, so 也保证了只有改数据没有分发过的话才会再次分发。
再次回调时的问题(数据回灌) 先举一个例子, 我们在请求网络然后通过 LiveDate 从 ViewModel 中订阅数据回调, 然后通过结果进行逻辑处理。 如果我们已经请求过后处理完毕了。 如果这个时候去修改了系统文字大小, 然后再次进入界面时, 网络请求的那个数据会再次回调过来, 那么再处理逻辑, 可能会出现问题。 在 Activity 由内存不足或者配置发生改变了(比如修改了系统文字大小再次进入), 界面会再次还原重建, 这个时候 ViewModel 还是原来的那个呢。 所以再次进入的时候, 会把之前设置的值再次回到到当前界面。 所以我们需要非常谨慎谨慎再谨慎。 在使用 LiveData 时已经要考虑到该情况。