内容简介:LiveData 和 ViewModel 是 Google 官方的 MVVM 架构的一个组成部分。巧了,昨天分析了一个问题是 ViewModel 的生命周期导致的。今天又遇到了一个问题是 LiveData 通知导致的。而 ViewModel 的生命周期和 LiveData 的通知机制是它们的主要责任。所以,就这个机会我们也来分析一下 LiveData 通知的实现过程。今天所遇到的问题是这样的,有两个页面 A 和 B,A 是一个 Fragment ,是一个列表的展示页;B 是其他的页面。首先,A 会更新页面,
LiveData 和 ViewModel 是 Google 官方的 MVVM 架构的一个组成部分。巧了,昨天分析了一个问题是 ViewModel 的生命周期导致的。今天又遇到了一个问题是 LiveData 通知导致的。而 ViewModel 的生命周期和 LiveData 的通知机制是它们的主要责任。所以,就这个机会我们也来分析一下 LiveData 通知的实现过程。
- 关于 ViewModel 的生命周期: 《浅谈 LiveData 的通知机制》 ;
- 关于 MVVM 设计模式的基本应用,你可以参考这篇文章: 《Android 架构设计:MVC、MVP、MVVM和组件化》 .
1、一个 LiveData 的问题
今天所遇到的问题是这样的,
有两个页面 A 和 B,A 是一个 Fragment ,是一个列表的展示页;B 是其他的页面。首先,A 会更新页面,并且为了防止连续更新,再每次更新之前需要检查一个布尔值,只有为 false 的时候才允许从网络加载数据。每次加载数据之前会将该布尔值置为 true,拿到了结果之后置为 false. 这里拿到的结果是借助 LiveData 来通知给页面进行更新的。
现在,A 打开了 B,B 中对列表中的数据进行了更新,然后发了一条类似于广播的消息。此时,A 接收了消息并进行数据加载。过了一段时间,B 准备退出,再退出的时候又对列表中的项目进行了更新,所以此时又发出了一条消息。
B 关闭了,我们回到了 A 页面。但是,此时,我们发现 A 页面中的数据只包含了第一次的数据更新,第二次的数据更新没有体现在列表中。
用代码来描述的话大致是下面这样,
// 类 A public class A extends Fragment { private boolean loading = false; private MyViewModel vm; // ...... /** * Register load observer. */ public void registerObservers() { vm.getData().observe(this, resources -> { loading = false; // ... show in list }) } /** * Load data from server. */ public void loadData() { if (loading) return; loading = true; vm.load(); } /** * On receive message. */ public void onReceive() { loadData(); } } public class B extends Activity { public void doBusiness1() { sendMessage(MSG); // Send message when on foreground. } @Override public void onBackpressed() { // .... sendMessage(MSG); // Send message when back } } public class MyViewModel extends ViewModel { private MutableLiveData<Resoucres<Object>> data; public MutableLiveData<Resoucres<Object>> getData() { if (data == null) { data = new MutableLiveData<>(); } return data; } public void load() { Object result = AsyncGetData.getData(); // Get data if (data != null) { data.setValue(Resouces.success(result)); } } } 复制代码
A 打开了 B 之后,A 处于后台,B 处于前台。此时,B 调用 doBusiness1()
发送了一条消息 MSG,A 中在 onReceive()
中收到消息,并调用 loadData()
加载数据。然后,B 处理完了业务,准备退出的时候发现其他数据发生了变化,所以又发了一条消息,然后 onReceive()
中收到消息,并调用 loadData()
. 但此时发现 loading 为 true. 所以,我们后来对数据的修改没有体现到列表上面。
2、问题的原因
如果用上面的示例代码作为例子,那么出现问题的原因就是当 A 处于后台的时候。虽然调用了 loadData()
并且从网络中拿到了数据,但是调用 data.setValue()
方法的时候无法通知到 A 中。所以, loading = false
这一行无法被调用到。第二次发出通知的时候,一样调用到了 loadData()
,但是因为此时 loading
为 true,所以并没有执行加载数据的操作。而当从 B 中完全回到 A 的时候,第一次加载的数据被 A 接收到。所以,列表中的数据是第一次加载时的数据,第二次加载事件丢失了。
解决这个问题的方法当然比较简单,可以当接收到事件的时候使用布尔变量监听,然后回到页面的时候发现数据发生变化再执行数据加载:
// 类 A public class A extends Fragment { private boolean dataChanged; /** * On receive message. */ public void onReceive() { dataChanged = true; } @Override public void onResume() { // ... if (dataChanged) { loadData(); } } } 复制代码
对于上面的问题,当我们调用了 setValue()
之后将调用到 LiveData 类的 setValue()
方法,
@MainThread protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); } 复制代码
这里表明该方法必须在主线程中被调用,最终事件的分发将会交给 dispatchingValue()
方法来执行:
private 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<T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { // 发送事件 considerNotify(iterator.next().getValue()); if (mDispatchInvalidated) { break; } } } } while (mDispatchInvalidated); mDispatchingValue = false; } 复制代码
然后,会调用 considerNotify()
方法来最终将事件传递出去,
private void considerNotify(ObserverWrapper observer) { // 这里会因为当前的 Fragment 没有处于 active 状态而退出方法 if (!observer.mActive) { return; } if (!observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); } 复制代码
这里会因为当前的 Fragment 没有处于 active 状态而退出 considerNotify()
方法,从而消息无法被传递出去。
3、LiveData 的通知机制
LiveData 的通知机制并不复杂,它的类主要包含在 livedata-core
包下面,总共也就 3 个类。LiveData 是一个抽象类,它有一个默认的实现就是 MutableLiveData.
LiveData 主要依靠内部的变量 mObservers
来缓存订阅的对象和订阅信息。其定义如下,使用了一个哈希表进行缓存和映射,
private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers = new SafeIterableMap<>(); 复制代码
每当我们调用一次 observe()
方法的时候就会有一个映射关系被加入到哈希表中,
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) { 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); } 复制代码
从上面的代码我们可以看出,添加到映射关系中的类会先被包装成 LifecycleBoundObserver
对象。然后使用该对象对 owner 的生命周期进行监听。
这的 LifecycleBoundObserver
和 ObserverWrapper
两个类的定义如下,
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver { @NonNull final LifecycleOwner mOwner; LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) { super(observer); mOwner = owner; } @Override boolean shouldBeActive() { return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (mOwner.getLifecycle().getCurrentState() == DESTROYED) { removeObserver(mObserver); return; } activeStateChanged(shouldBeActive()); } @Override boolean isAttachedTo(LifecycleOwner owner) { return mOwner == owner; } @Override void detachObserver() { mOwner.getLifecycle().removeObserver(this); } } private abstract class ObserverWrapper { final Observer<T> mObserver; boolean mActive; int mLastVersion = START_VERSION; ObserverWrapper(Observer<T> observer) { mObserver = observer; } abstract boolean shouldBeActive(); boolean isAttachedTo(LifecycleOwner owner) { return false; } void detachObserver() {} void activeStateChanged(boolean newActive) { if (newActive == mActive) { return; } mActive = newActive; boolean wasInactive = LiveData.this.mActiveCount == 0; LiveData.this.mActiveCount += mActive ? 1 : -1; if (wasInactive && mActive) { onActive(); } if (LiveData.this.mActiveCount == 0 && !mActive) { onInactive(); } if (mActive) { dispatchingValue(this); } } } 复制代码
上面的类中我们先来关注 LifecycleBoundObserver
中的 onStateChanged()
方法。该方法继承自 LifecycleObserver
. 这里的 Lifecycle.Event
是一个枚举类型,定义了一些与生命周期相关的枚举值。所以,当 Activity 或者 Fragment 的生命周期发生变化的时候会回调这个方法。从上面我们也可以看出,该方法内部又调用了基类的 activeStateChanged()
方法,该方法主要用来更新当前的 Observer 是否处于 Active 的状态。我们上面无法通知也是因为在这个方法中 mActive 被置为 false 造成的。
继续看 activeStateChanged()
方法,我们可以看出在最后的几行中,它调用了 dispatchingValue(this)
方法。所以,当 Fragment 从处于后台切换到前台之后,会将当前缓存的值通知给观察者。
那么值是如何缓存的,以及缓存了多少值呢?回到之前的 setValue()
和 dispatchingValue()
方法中,我们发现值是以一个单独的变量进行缓存的,
private volatile Object mData = NOT_SET; 复制代码
因此,在我们的示例中,当页面从后台切换到前台的时候,只能将最后一次缓存的结果通知给观察者就真相大白了。
以上所述就是小编给大家介绍的《浅谈 LiveData 的通知机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Netty 之异步通知机制
- Java并发 -- 等待-通知机制
- XWiki 10.2 发布,更换默认主题 调整通知机制
- iOS 推送通知及通知扩展
- Chrome 已将消息通知整合到 Windows 10 的通知中心
- ZMQ通知中转
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Laravel框架关键技术解析
陈昊、陈远征、陶业荣 / 电子工业出版社 / 2016-7 / 79.00元
《Laravel框架关键技术解析》以Laravel 5.1版本为基础,从框架技术角度介绍Laravel构建的原理,从源代码层次介绍Laravel功能的应用。通过本书的学习,读者能够了解Laravel框架实现的方方面面,完成基于该框架的定制化应用程序开发。 《Laravel框架关键技术解析》第1章到第4章主要介绍了与Laravel框架学习相关的基础部分,读者可以深入了解该框架的设计思想,学习环......一起来看看 《Laravel框架关键技术解析》 这本书的介绍吧!