当前位置:   article > 正文

Android 使用 LiveData 实现 EventBus

android 用livedata实现eventbus

code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群

作者:Lin_YT
链接:https://www.jianshu.com/p/f9f86bd928a1
声明:本文已获Lin_YT授权发表,转发等请联系原作者授权

绪论

本文是学习了大佬的文章后,自己去动手实践后写的一篇学习笔记。大佬的文章写得比较好,我自己去写未必描述得那么清楚????,所以本文很多地方都直接引用了大佬的文章。

项目源码:https://github.com/LinYaoTian/LiveDataBus

Tip:阅读本文最好对 Jetpack 的 LIfeCycle 和 LiveData 有初步的了解。

引用:

  • Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus

  • 基于LiveData实现事件总线思路和方案

  • Android Jetpack架构组件(二)一文带你了解Lifecycle(使用篇)

  • Android Jetpack架构组件(四)一文带你了解LiveData(使用篇)

  • Android Jetpack架构组件(五)一文带你了解LiveData(原理篇)

基本概念

对于 Android 系统来说,消息传递是最基本的组件,每一个 App 内的不同页面,不同的组件都在进行消息传递。

消息传递既可以用于 Android 四大组件之前的通信,也可以用于异步线程和主线程之前的通信。

对于 Android 开发者来说,经常使用的消息传递方式有很多种,从最早的 Handler、BroadcastReceiver 、接口回调,到最近几年的流行的通信总线类框架 EventBus、RxBus。

EventBus

说到 Android 的通信总线类框架就不得不提到 EventBus 。

EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递。EventBus 可以代替 Android 传统的 Intent、Handler、Broadcast 或接口回调,在 Fragment、Activity、Service 线程之间传递数据,执行方法。

EventBus 最大的特点就是:简洁、解耦。

在没有 EventBus 之前我们通常用广播来实现监听,或者自定义接口函数回调,有的场景我们也可以直接用Intent携带简单数据,或者在线程之间通过 Handler 处理消息传递。但无论是广播还是 Handler 机制远远不能满足我们高效的开发。EventBus 简化了应用程序内各组件间、组件与后台线程间的通信。EventBus 一经推出,便受到广大开发者的推崇。

现在看来,EventBus 给 Android 开发者世界带来了一种新的框架和思想,就是消息的发布和订阅。这种思想在其后很多框架中都得到了应用。

image

订阅/发布模式

订阅发布模式定义了一种“一对多”的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。

订阅/发布模式和观察者模式两者非常相似,个人觉得订阅/发布模式是观察者模式的一种增强版。

两者的区别有:

  • 观察者模式下,观察者和被观察者之前直接存在依赖关系。

  • 而订阅/发布模式下,订阅者和发布者之前多了一个调度中心,避免了订阅者和发布者的依赖关系。

image

详情可以看这篇文章:https://juejin.im/post/5a14e9edf265da4312808d86

LiveData

本文是通过 LiveData 去实现 EventBus ,这里先介绍下 LiveData。

LiveData 如同它的名字一样,是一个可观察的数据持有者,和常规的 observable 不同,LiveData 是可以具有生命周期感知的,这意味着它能够在 Activity、Fragment、Service 中正确的处理生命周期。

实际上 LiveData 并不能单独起作用,它依赖于 Jectpack 的 LifeCycle 组件,因为 LifeCycle 已经在 Activity 的父类中被封装好了,在我们使用 LiveData 的过程中是基本是无感知的,所以这里就简单提一下 LifeCycle 好了。

LifeCycle 的作用

  • 管理组件的生命周期。

  • 让第三方业务能在自己内部就能拿到依赖的组件的生命周期,便于及时叫停,避免错过执行时机。

想继续了解的同学可以看下这两篇文章:

  • https://juejin.im/post/5db27753518825648f2ef5c9(使用篇)

  • https://juejin.im/post/5db27808f265da4d1c699544(原理篇)

下面继续说 LiveData:

image

LiveData 的数据源一般是 ViewModel,也可以是其他可以更新的 LiveData 组件。

一般我们使用 LiveData 的 observe(),当数据更新后,LiveData 会通知它的所有活跃的观察者。与 RxJava 不同的,LiveData 只会通知活跃的观察者,例如 Activity 位于 Destroyed 状态时是不活跃的,因此不会收到通知。

当然我们也可以使用 LiveData 的 observerForever() 方法进行订阅,区别是 observerForever() 不会受到 Activity 等组件的生命周期的影响,只要数据更新就会收到通知。

LiveData 的简单使用:

  1. public class MainActivity extends AppCompatActivity {
  2.    private static final String TAG="MainActivity";
  3.     @Override
  4.     protected void onCreate(Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.         setContentView(R.layout.activity_main);
  7.         // 创建 LiveData 对象
  8.         MutableLiveData<String> mutableLiveData  = new MutableLiveData<>();
  9.         // 开始订阅
  10.         mutableLiveData.observe(this, new Observer<String>() {
  11.             @Override
  12.             public void onChanged(@Nullable final String s) {
  13.                 Log.d(TAG, "onChanged:"+s);
  14.             }
  15.         });
  16.         // 更新数据,最终会回调上面的 onChange() 方法
  17.         mutableLiveData.postValue("Android进阶三部曲");
  18.     }
  19. }

LiveData 的更多详情可以参考这篇文章 https://juejin.im/post/5db76cad5188250e1242a324(使用篇)

通过 LiveData 实现的优点是什么?

EventBus 是业界知名的通信类总线库,但它也存在许多被人诟病的缺点:

  1. 需要手动的注册和反注册,稍不小心可能会造成内存泄露。

  2. 使用 EventBus 出错时难以跟踪出错的事件源。

  3. 每个事件都要定义一个事件类,容易造成类膨胀。

而通过 LiveData 实现的 LiveDataBus 的具有以下优点:

  1. 具有生命周期感知能力,不用手动注册和反注册。

  2. 具有唯一的可信事件源。

  3. 以字符串区分每一个事件,避免类膨胀。

  4. LiveData 为 Android 官方库,更加可靠。

原理

LiveDataBus 的组成
  • 消息:消息可以是任何的 Object,可以定义不同类型的消息,如 Boolean、String。也可以定义自定义类型的消息。

  • 消息通道:LiveData 扮演了消息通道的角色,不同的消息通道用不同的名字区分,名字是 String 类型的,可以通过名字获取到一个 LiveData 消息通道。

  • 消息总线:消息总线通过单例实现,不同的消息通道存放在一个 HashMap 中。

  • 订阅:订阅者通过 getChannel() 获取消息通道,然后调用 observe() 订阅这个通道的消息。

  • 发布:发布者通过 getChannel() 获取消息通道,然后调用 setValue() 或者 postValue() 发布消息。

LiveDataBus 原理图

image

简单实现

通过 LiveData 我们可以非常简单的实现一个事件发布/订阅框架:

  1. public final class LiveDataBus {
  2.     private final Map<String, MutableLiveData<Object>> bus;
  3.     private LiveDataBus() {
  4.         bus = new HashMap<>();
  5.     }
  6.     private static class SingletonHolder {
  7.         private static final LiveDataBus DATA_BUS = new LiveDataBus();
  8.     }
  9.     public static LiveDataBus get() {
  10.         return SingletonHolder.DATA_BUS;
  11.     }
  12.     public <T> MutableLiveData<T> getChannel(String target, Class<T> type) {
  13.         if (!bus.containsKey(target)) {
  14.             bus.put(target, new MutableLiveData<>());
  15.         }
  16.         return (MutableLiveData<T>) bus.get(target);
  17.     }
  18.     public MutableLiveData<Object> getChannel(String target) {
  19.         return getChannel(target, Object.class);
  20.     }
  21. }

没错,上面短短的 27 行代码我们就实现了一个事件发布/订阅框架!

问题一

LiveData 一时使用一时爽,爽完了之后我们发现这个简易的 LiveDataBus 存在一个问题,就是订阅者会收到订阅之前发布的消息,类似于粘性消息。对于一个消息总线来说,粘性消息和非粘性消息都是必须支持的,下面我们来看一下如何解决这个问题。

我们先看一下为什么会出现这个问题:

LiveData 的订阅方法

android.arch.lifecycle.LiveData

  1. @MainThread
  2.    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
  3.        assertMainThread("observe");
  4.        // 当前绑定的组件(activity or fragment)状态为DESTROYED的时候, 则会忽视当前的订阅请求
  5.        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
  6.            return;
  7.        }
  8.        // 转为带生命周期感知的观察者包装类
  9.        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
  10.        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
  11.        // 对应观察者只能与一个owner绑定
  12.        if (existing != null && !existing.isAttachedTo(owner)) {
  13.            throw new IllegalArgumentException("Cannot add the same observer"
  14.                    + " with different lifecycles");
  15.        }
  16.        if (existing != null) {
  17.            return;
  18.        }
  19.        // lifecycle注册
  20.        owner.getLifecycle().addObserver(wrapper);
  21.    }

可以看到,LiveData 内部会将我们的传入参数包装成 wrapper ,然后存在一个 Map 中,最后通过 LifeCycle 组件添加观察者。

LiveData 的更新数据方法

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

这里就只看下 setValue() 方法就好了

android.arch.lifecycle.LiveData

  1. @MainThread
  2. protected void setValue(T value) {
  3.     assertMainThread("setValue");
  4.     // 发送版本+1
  5.     mVersion++;
  6.     mData = value;
  7.     // 信息分发
  8.     dispatchingValue(null);
  9. }

记住这里的 mVersion,它本问题关键,每次更新数据都会自增,默认值是 -1然后我们跟进下 dispatchingValue() 方法:

android.arch.lifecycle.LiveData

  1. void dispatchingValue(@Nullable ObserverWrapper initiator) {
  2. // mDispatchingValue的判断主要是为了解决并发调用dispatchingValue的情况
  3. // 当对应数据的观察者在执行的过程中, 如有新的数据变更, 则不会再次通知到观察者
  4. // 所以观察者内的执行不应进行耗时工作
  5. if (mDispatchingValue) {
  6. mDispatchInvalidated = true;
  7. return;
  8. }
  9. mDispatchingValue = true;
  10. do {
  11. mDispatchInvalidated = false;
  12. if (initiator != null) {
  13. // 这里
  14. considerNotify(initiator);
  15. initiator = null;
  16. } else {
  17. for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
  18. mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
  19. // 这里
  20. considerNotify(iterator.next().getValue());
  21. if (mDispatchInvalidated) {
  22. break;
  23. }
  24. }
  25. }
  26. } while (mDispatchInvalidated);
  27. mDispatchingValue = false;
  28. }

可以看到,无论条件判断,最终都会执行 considerNotify() 方法,所以我们继续跟进:

android.arch.lifecycle.LiveData

  1. private void considerNotify(ObserverWrapper observer) {
  2.         if (!observer.mActive) {
  3.             return;
  4.         }
  5.         if (!observer.shouldBeActive()) {
  6.             observer.activeStateChanged(false);
  7.             return;
  8.         }
  9.             // 判断 version
  10.         if (observer.mLastVersion >= mVersion) {
  11.             return;
  12.         }
  13.         observer.mLastVersion = mVersion;
  14.         //noinspection unchecked
  15.         observer.mObserver.onChanged((T) mData);
  16.     }

终于到了最关键的时候了!!如果 ObserverWrapper 的 mLastVersion 小于 LiveData 的 mVersion,那么就会执行的 onChange() 方法去通知观察者数据已更新。

而 ObserverWrapper.mLastVersion 的默认值是 -1, LiveData 只要更新过数据,mVersion 就肯定会大于 -1,所以订阅者会马上收到订阅之前发布的最新消息!!

问题二

我们看一下 LiveData 的 postValue() 方法的注释:

image

可以看到,注释中说,如果在多线程中同一个时刻,多次调用了 postValue() 方法,只有最后次调用的值会得到更新。也就是此方法是有可能会丢失事件!!!

看下源码:

android.arch.lifecycle.LiveData

  1.     private final Runnable mPostValueRunnable = new Runnable() {
  2.         @Override
  3.         public void run() {
  4.             Object newValue;
  5.             synchronized (mDataLock) {
  6.                 newValue = mPendingData;
  7.                 // 设置 mPendingData 为 not_set
  8.                 mPendingData = NOT_SET;
  9.             }
  10.             setValue((T) newValue);
  11.         }
  12.     };
  13.     protected void postValue(T value) {
  14.         boolean postTask;
  15.         synchronized (mDataLock) {
  16.             // 判断 postTask 是否为 not_set
  17.             postTask = mPendingData == NOT_SET;
  18.             mPendingData = value;
  19.         }
  20.         if (!postTask) {
  21.             return;
  22.         }
  23.         ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
  24.     }

从上面的源码就可以很容易看出,postValue 只是把传进来的数据先存到 mPendingData,然后往主线程抛一个 Runnable,在这个 Runnable 里面再调用 setValue 来把存起来的值真正设置上去,并回调观察者们。而如果在这个 Runnable 执行前多次 postValue,其实只是改变暂存的值 mPendingData,并不会再次抛另一个 Runnable。

最终实现

明白了问题根源所在,我们就好解决问题了。

解决这个问题的方案有多种,其中美团大佬Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus 使用的反射的方式修改 LiveData 中的 mVersion 值去实现。还有另一个方案基于LiveData实现事件总线思路和方案,此方案是基于自定义观察者包装类,因为粘性消息最终会调用到 Observer#onChange() 方法,因此我们自定义 Observer 包装类,自己维护实际的订阅消息数,来判断是否需要触发真正的 onChange() 方法。本文使用的是第二种方案。

第一步:自定义 Observer 包装类

为了业务的拓展性,这里我们定义先一个 Base 基类:

  1. internal open class BaseBusObserverWrapper<T>(private val mObserver: Observer<in T>, private val mBusLiveData: BusLiveData<T>) : Observer<T> {
  2.     private val mLastVersion = mBusLiveData.version
  3.     private val TAG = "BaseBusObserverWrapper"
  4.     override fun onChanged(t: T?) {
  5.         Logger.d(TAG,"msg receiver = " + t.toString())
  6.         if (mLastVersion >= mBusLiveData.version){
  7.             // LiveData 的版本号没有更新过,说明并没有新数据,只是因为
  8.             // 当前 Observer 的版本号比 LiveData 低导致的调用 onChange()
  9.             return
  10.         }
  11.         try {
  12.             mObserver.onChanged(t)
  13.         }catch (e:Exception){
  14.             Logger.e(TAG,"error on Observer onChanged() = " + e.message)
  15.         }
  16.     }
  17.     open fun isAttachedTo(owner: LifecycleOwner) = false
  18. }

这里我们保存了 LiveData 的 mVersion 值,每次执行 onChange() 时都先判断一些 LiveData 是否更新过数据,如果没有则不执行观察者的 Observer.onChange() 方法。

然后定义两个子类,BusLifecycleObserver 和 BusAlwaysActiveObserver ,其中 BusLifecycleObserver 用于 LiveData#observer() 方法订阅的事件,而 BusAlwaysActiveObserver 用于 LiveData#observerForever() 方法订阅的事件。

  1. internal class BusLifecycleObserver<T>(private val observer: Observer<in T>, private val owner: LifecycleOwner, private val liveData: BusLiveData<T>)
  2.     : BaseBusObserverWrapper<T>(observer,liveData),LifecycleObserver{
  3.     @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
  4.     fun onDestroy(){
  5.         liveData.removeObserver(observer)
  6.         owner.lifecycle.removeObserver(this)
  7.     }
  8. }

对于 BusLifecycleObserver,在生命周期组件处于 Destroyed 时,需要把观察者移除。

  1. internal class BusAlwaysActiveObserver<T>(private val mObserver: Observer<in T>, private val mBusLiveData: BusLiveData<T>)
  2.     : BaseBusObserverWrapper<T>(mObserver, mBusLiveData)

对于 BusAlwaysActiveObserver,观察者不受组件生命周期的影响,因此不需要在组件 Destroyed 时移除。

第二步:自定义 LiveData 类

注意:因为 LiveData 的 getVersion() 是包访问级别的!所以 BusLiveData 必须定义到与 LiveData 同一个包内,即 androidx.lifecycle 包下,因此需要你自己创建一个同名的包并将 BusLiveData 放到里面。

如果你的项目工程没有引入 androidx,也可以使用 v7 包下的 android.arch.lifecycle,方法同理,不过要注意的是 android.arch.lifecycle 包下 LiveData 的方法参数的泛型是没有型变的,因此直接复制这里代码会有点问题,需要你自己根据编译器的提示修改下。

  1. class BusLiveData<T>(private val mKey:String) : MutableLiveData<T>() {
  2.     private val TAG = "BusLiveData"
  3.     private val mObserverMap: MutableMap<Observer<in T>, BaseBusObserverWrapper<T>> = mutableMapOf()
  4.     @MainThread
  5.     override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
  6.         val exist = mObserverMap.getOrPut(observer,{
  7.             BusLifecycleObserver(observer,owner,this).apply {
  8.                 mObserverMap[observer] = this
  9.                 owner.lifecycle.addObserver(this)
  10.             }
  11.         })
  12.         super.observe(owner, exist)
  13.         Logger.d(TAG,"observe() called with: owner = [$owner], observer = [$observer]")
  14.     }
  15.     @MainThread
  16.     override fun observeForever(observer: Observer<in T>) {
  17.         super.observeForever(observer)
  18.         val exist = mObserverMap.getOrPut(observer ,{
  19.             BusAlwaysActiveObserver(observer,this).apply {
  20.                 mObserverMap[observer] = this
  21.             }
  22.         })
  23.         super.observeForever(exist)
  24.         Logger.d(TAG, "observeForever() called with: observer = [$observer]")
  25.     }
  26.     @MainThread
  27.     fun observeSticky(owner: LifecycleOwner, observer: Observer<T>) {
  28.         super.observe(owner, observer)
  29.         Logger.d(TAG, "observeSticky() called with: owner = [$owner], observer = [$observer]")
  30.     }
  31.     @MainThread
  32.     fun observeStickyForever(observer: Observer<T>){
  33.         super.observeForever(observer)
  34.         Logger.d(TAG, "observeStickyForever() called with: observer = [$observer]")
  35.     }
  36.     @MainThread
  37.     override fun removeObserver(observer: Observer<in T>) {
  38.         val exist = mObserverMap.remove(observer) ?: observer
  39.         super.removeObserver(exist)
  40.         Logger.d(TAG, "removeObserver() called with: observer = [$observer]")
  41.     }
  42.     @MainThread
  43.     override fun removeObservers(owner: LifecycleOwner) {
  44.         mObserverMap.iterator().forEach {
  45.             if (it.value.isAttachedTo(owner)) {
  46.                 mObserverMap.remove(it.key)
  47.             }
  48.         }
  49.         super.removeObservers(owner)
  50.         Logger.d(TAG, "removeObservers() called with: owner = [$owner]")
  51.     }
  52.     
  53.     override fun postValue(value: T) {
  54.        mMainHandler.post {
  55.            setValue(value)
  56.        }
  57.     }
  58.     @MainThread
  59.     override fun onInactive() {
  60.         super.onInactive()
  61.         if (!hasObservers()) {
  62.             // 当 LiveData 没有活跃的观察者时,可以移除相关的实例
  63.             LiveDataBusCore.getInstance().mBusMap.remove(mKey)
  64.         }
  65.         Logger.d(TAG, "onInactive() called")
  66.     }
  67.     @MainThread
  68.     public override fun getVersion(): Int {
  69.         return super.getVersion()
  70.     }
  71. }

代码比较短和简单,要点就是,对于非粘性消息,即 observer() 和 observerForever() ,我们需要使用自定义的包装类包装处理。对于粘性消息,则直接使用 LiveData 默认实现即可。

同时重写 postValue() 方法,内部通过 MainHandler.post() 的方式去执行 setValue() 去避免 postValue() 会丢失数据的问题。

第三步:定义管理类
  1. internal class LiveDataBusCore {
  2. companion object{
  3. @JvmStatic
  4. private val defaultBus = LiveDataBusCore()
  5. @JvmStatic
  6. fun getInstance() = defaultBus
  7. }
  8. internal val mBusMap : MutableMap<String, BusLiveData<*>> by lazy {
  9. mutableMapOf<String, BusLiveData<*>>()
  10. }
  11. fun <T> getChannel(key: String) : BusLiveData<T> {
  12. return mBusMap.getOrPut(key){
  13. BusLiveData<T>(key)
  14. } as BusLiveData<T>
  15. }
  16. }
第四步:定义入口类
  1. class LiveDataBus {
  2.     companion object{
  3.         @JvmStatic
  4.         @Synchronized
  5.         fun <T> getSyn(key: String) : BusLiveData<T>{
  6.             return get(key)
  7.         }
  8.       
  9.         @JvmStatic
  10.         fun <T> get(key: String) : BusLiveData<T>{
  11.             return LiveDataBusCore.getInstance().getChannel(key)
  12.         }
  13.       
  14.         private fun <T> get(key: String, type: Class<T>) : BusLiveData<T> {
  15.             return LiveDataBusCore.getInstance().getChannel(key)
  16.         }
  17.         @JvmStatic
  18.         fun <E> of(clz: Class<E>): E {
  19.             require(clz.isInterface) { "API declarations must be interfaces." }
  20.             require(clz.interfaces.isEmpty()) { "API interfaces must not extend other interfaces." }
  21.             return Proxy.newProxyInstance(clz.classLoader, arrayOf(clz), InvocationHandler { _, method, _->
  22.                 return@InvocationHandler get(
  23.                         // 事件名以集合类名_事件方法名定义
  24.                         // 以此保证事件的唯一性
  25.                         "${clz.canonicalName}_${method.name}",
  26.                         (method.genericReturnType as ParameterizedType).actualTypeArguments[0].javaClass)
  27.             }) as E
  28.         }
  29.     }
  30. }

使用:

  1. class TestLiveDataBusActivity : AppCompatActivity() {
  2.   
  3.     companion object{
  4.         private const val TAG = "TestLiveDataBusActivity"
  5.     }
  6.     override fun onCreate(savedInstanceState: Bundle?) {
  7.         super.onCreate(savedInstanceState)
  8.         setContentView(R.layout.activity_test_live_data_bus)
  9.         LiveDataBus.get<String>("testObserver").observe(this, Observer<String> {
  10.             Log.d(TAG, "testObserver = $it")
  11.             test_observer_id.text = it
  12.         })
  13.         
  14.         LiveDataBus.get<String>("testObserver").postValue("new value")
  15.     }
  16. }

LiveDataBus 还提供了一个 of() 方法,用于提供用于事件约束,什么是时间约束呢?就是说我们的现在观察者和发布者获取消息渠道的 key 是一个字符串,在使用的过程中可能会出现,发布者的 key 是 itO 而观察者的 key 误输入成 it0 的情况,所以这里我们可以模仿 Retrofit 请求动态代理的做法,在使用的过程中,我们需要先定义一个接口:

  1. interface TestLiveEvents {
  2.     fun event1(): MutableLiveData<String>
  3. }

使用:

  1. fun main() {
  2.     LiveDataBus
  3.             .of(TestLiveEvents::class.java)
  4.             .event1()
  5.             .postValue("new value")
  6. }

总结

借助于 Android 官方提供的 LiveData ,我们可以非常方便地实现自己的 LiveDataBus,全部文件也就只有以上几个类,同时我们还避免了 EventBus 的许多缺点!

LiveDataBus 的源码:https://github.com/LinYaoTian/LiveDataBus

如果文章有任何问题,欢迎在评论区指正。

相关阅读

LiveData+Retrofit 网络请求实战
Jetpack 源码解析—LiveData的使用及工作原理
Android 官方架构组件(二)——LiveData
Glide 源码解析之监听生命周期
Flutter 2020首个稳定版 1.17 重磅发布:多个新增特性

如果你有写博客的好习惯

欢迎投稿

点个在看,小生感恩❤️

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/279809
推荐阅读
相关标签
  

闽ICP备14008679号