当前位置:首页 » 《随便一记》 » 正文

重复造轮子之LiveDataBus与PageEventBus_Android架构交流群:519844232

18 人参与  2021年11月26日 14:43  分类 : 《随便一记》  评论

点击全文阅读


背景

Android的消息总线框架近几年流行莫过于的EventBus,RxBus,一般来讲它已经足够好用,简洁、解耦,我们能够很方便的进行消息传递,那为什么我们现在又要再造一个消息总线的框架的轮子呢?

这就要说到今天的主角LiveData了,LiveData天生为观察者模式,与EventBus的功能有很大重合,而且作为jetpack的一员,与Lifecycle和ViewModel有很好配合,现在网上也有很多要用LiveData取代EventBus的声音,它主要有以下优点:

  • 感知生命周期
  • 不需要调用反注册方法

这里盗一张图,看一下LiveData的结构:

EventBus发送的消息是全局,在某些情况下,我们发送的消息只希望在页面内部接收到,例如同一个Activity的两个Fragment,这时候使用Event就很麻烦,而传统方式是利用Activity作为桥梁,使用接口的方式进行消息传递,但这样需要定义接口和回调,又太过麻烦,这时候就轮到LiveData登场了(其实还要加上ViewModel),这其实也是Google推荐的实现方式。下面我们主要讲讲复用LiveData实现方式

具体实现

使用LiveData实现消息总线非常简单,一个文件即可解决,网上很多例子,这里非常简单的实现了一下:

public class LiveEventBus {

    public static LiveEventBus get() {
       return LiveEventBusHolder.instance;
    }

    private static class LiveEventBusHolder {
        static LiveEventBus instance = new LiveEventBus();
    }

    private Map<String, MutableLiveData<? extends LiveBusEvent>> mBus = new ArrayMap<>();

    @NonNull
    public <T extends LiveBusEvent> MutableLiveData<T> of(@NonNull Class<T> clazz) {
        String eventName = clazz.getName();
        MutableLiveData liveData = mBus.get(eventName);
        if (liveData == null) {
            liveData = new MutableLiveData();
            mBus.put(eventName, liveData);
        }

        return liveData;
    }

    public <T extends LiveBusEvent> void post(@NonNull T event) {
        MutableLiveData liveData = of(event.getClass());
        liveData.setValue(event);

    }

    interface LiveBusEvent {
    }

}

监听消息

LiveEventBus.get()
    .of(MyEvent.class)
    .observe(this, new Observer<MyEvent>() {
            @Override
            public void onChanged(MyEvent myEvent) {

            }
        });

发送消息

LiveEventBus.get().post(new MyEvent("hello world"));

这样一个事件总线就完成了。

但是,LiveData谡消息总线,有一个非常严重的问题,那就是它只支持粘性事件,也就是说,如果我们先发送一个消息,再通过observe监听消息,那么就会立刻接收到一个消息,这其实是我们不希望看到的,所以接下来主要需要解决粘性事件的问题。

粘性事件分析及解析

这个问题其实已经有很多文章分析过了,这里就不重复分析了,大家可以看看美团的这篇文章: Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus

说一下关键原因,在LiveData中有一个int类型的成员变量version

public abstract class LiveData<T> {

    static final int START_VERSION = -1;

    private int mVersion = START_VERSION;

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

    }

}

这个version是用来记录LiveData设置值的次数的,初始为-1,每次加1,而注册observer时,如果当Lifecycle处理活动状态后会立刻分发事件,最后会立刻回调下面这个方法:

private void considerNotify(ObserverWrapper observer) {

    if (!observer.mActive) {
        return;
    }

    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }

    if (observer.mLastVersion >= mVersion) {
        //主要是这里
        return;
    }

    observer.mLastVersion = mVersion;

    //noinspection unchecked
    observer.mObserver.onChanged((T) mData);

}

observer是一个ObseverWraper,为Observer的包装类,mLastVersion的值初始都会-1,所以新注册一个类时,如果之前只要有设置过值,LiveData的mVersion就不会为-1,会比 observer.mLastVersion,我们注册的Observer就会接收到事件。

所以要解决粘性事件的问题,自然而然的就会想到去改变version的值,然而不管是mVersion还是mLastVersion,它们都是private的,无法直接拿到,所以就催生出反射获取的方法,美团的这篇文章就是这样解决的。

反射总归是不安全的,这里提一下我的解决方法:既然LiveData内部的成员我们无法修改,那为什么不自己实现,记录自己的version值呢?

public class BusLiveData<T> extends MutableLiveData<T> {

    private static final int VERSION_START = -1;
    private int activeVersion = VERSION_START;
    private ArrayMap<Observer<? super T>, BusObserver<? super T>> busObservers = new ArrayMap();

    @Override
    public void setValue(T value) {
        activeVersion++;
        super.setValue(value);
    }

    private BusObserver<? super T> createBusObserver(@NonNull Observer<? super T> observer, int latestVersion) {
        BusObserver<? super T> busObserver = busObservers.get(observer);
        if (busObserver == null) {
            busObserver = new BusObserver(observer, latestVersion);
            busObservers.put(observer, busObserver);
        } else {
            throw new IllegalArgumentException("Please not register same observer " + observer);
        }
        return busObserver;

    }

    @Override

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            return;
        }
        super.observe(owner, createBusObserver(observer, activeVersion));
    }

    public void observeSticky(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
           return;
        }
        super.observe(owner, createBusObserver(observer, VERSION_START));
    }

    @Override
    public void observeForever(@NonNull Observer<? super T> observer) {
        super.observeForever(createBusObserver(observer, activeVersion));
    }

    public void observeStickyForever(@NonNull Observer<T> observer) {
        super.observeForever(createBusObserver(observer, VERSION_START));
    }

    @Override
    public void removeObserver(@NonNull Observer<? super T> observer) {
        BusObserver<? super T> busObserver;
        if (observer instanceof BusObserver) {
            busObserver = (BusObserver) observer;
        } else {
            busObserver = busObservers.get(observer);
        }

        if (busObserver != null) {
            busObservers.remove(busObserver.realObserver);
            super.removeObserver(busObserver);
        }
    }

    private class BusObserver<M extends T> implements Observer<M> {

        private Observer<M> realObserver;
        private int lastVersion;

        public BusObserver(@NonNull Observer<M> realObserver, int lastVersion) {
            this.realObserver = realObserver;
            this.lastVersion = lastVersion;
        }

        @Override
        public void onChanged(M m) {
            if (activeVersion <= lastVersion) {
                return;
            }
            lastVersion = activeVersion;
            if (m != null) {
                realObserver.onChanged(m);
           }
        }
    }

}

由于在大部分情况下,消息传递是需要非粘性,我将observe的默认方法改成了非粘性的,并且提供了observeSticky来进行粘性事件的监听。

页面内通信

说到LiveData,就不得不提到ViewModel,所以要在页面页通信,只需要实现一个通用的ViewModel即可observeSticky单独出来使用

public class PageEventBus extends ViewModel {

    private static PageEventBus obtain(@NonNull ViewModelStoreOwner owner) {
        return new ViewModelProvider(owner, FACTORY).get(PageEventBus.class);
    }

    //省略其它
    public static ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            return (T) new PageEventBus();
        }
    };

}

通过自定义Event来发送消息

PageEventBus通过class来区分发送消息的类别,这个参考了EventBus,所以发送消息前需要先定义一个Event类,为规范使用,且必需继承LiveBusEvent接口,例如:

class FilterSearchEvent(val tag: String, val map: Map<String, Any>) : LiveBusEvent

activity包含一个ViewPager, ViewPager中有多个Fragment,Activity需要给Fragment发送消息,可以这样写

在Fragemnt onViewCreated之后注册监听,PageEventBus.get的参数必需为Activity或者Context,这样才能获取到ActivityViewModel. observe时传入viewLifecycleOwnerviewLifecycleOwner为Fragment生命周期特有的一个类,可以感知Fragment的onCreateView和onDestroyView的生命周期

PageEventBus.get(requireActivity())
    .of(FilterSearchEvent::class.java)
    .observe(viewLifecycleOwner, Observer {
        DuLogger.d("$mTag FilterSearchEvent: ${it.map}")
        if (it.tag != cateKey) return@Observer
        scrollTopRefresh()

    })

在Activity中发送消息

PageEventBus.get(this)
    .post(FilterSearchEvent(tag = filterHelper.getSelectTag(), map = map))

通过eventName发送消息

每个类型的事件都需要自定义类对于一些情况可能是不必要的,这里提供一种根据事件名称区分消息的方法 例如,我们要向商详发送名为pd_refresh_event的消息,先监听

PageEventBus.get(this)
            .ofEmpty("pd_refresh_event")
            .observe(this, Observer {
                getProductDetail()
            })

再发送消息

PageEventBus.get(this).postEmpty("pd_refresh_event")

可以看到,和自定义Event区别在于注册时用的ofEmpty, 发送用的postEmpty

全局通信

用法和PageEventBus一样,只是要将PageEventBus换成LiveEventBus,效果和EventBus一样

使用技巧与注意事项

postLatest

在使用LiveData时,我们会发现它提供了两个方法,分别为setValue和postValue, setValue只能在主线程中使用,postValue可以任意线程中使用,但实际上,postValue并不只是通过handle.post那么简单, 我们可以看一下它的源码:

protected void postValue(T value) {

    boolean postTask;

    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }

    if (!postTask) {
        return;
    }

    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);

}

可以看到,每次都会对mPendingData赋值,但在处理这次任务前只会post一次,这样防止了多次无意义的设置值。

为了防止混淆LiveEventBus/PageEventBus提供了post和postLatest分别对应LiveData的setValuepostValue, postLatest利用的是LiveData的post特性,将连续使用postLatest时,监听的处只会发收到一最后一条消息,作为消息通知时建议使用

例如:

LiveEventBus.get()
    .of(HitEvent::class.java)
    .observe(this, Observer {
        LogUtils.d("LiveEventBus HiltEvent receive: $it")
        doRefresh()
    })

//点击时,连续发送多个事件,只会收到最后一个

LiveEventBus.get().postLatest(HitEvent("HiltEvent 111"))
LiveEventBus.get().postLatest(HitEvent("HiltEvent 222"))
LiveEventBus.get().postLatest(HitEvent("HiltEvent 333"))

点击时连续发送3个事件,只会收到最后一个

LiveEventBus HiltEvent receive: HitEvent(content=HiltEvent 333)

kotlin中内部类的问题

在上面那个例子中,如果Observer中没有任何与外部类相关的代码,如下:

LiveEventBus.get()
    .of(HitEvent::class.java)
    .observe(this, Observer {
        LogUtils.d("LiveEventBus HiltEvent receive: $it")
    })

这里监听只打印了log,但是在打开多个Acitivity时,在第二次执行相同代码时会报如下错误:

Caused by: java.lang.IllegalArgumentException: Please not register same observer com.tory.demo.jetpack.HiltDemoActivity$initView$5@68383e
        at com.tory.library.utils.livebus.BusLiveData.createBusObserver(BusLiveData.java:36)
        at com.tory.library.utils.livebus.BusLiveData.observe(BusLiveData.java:46)
        at com.tory.library.utils.livebus.BusObservableWrapper.observe(BusObservableWrapper.java:63)
        at com.tory.demo.jetpack.HiltDemoActivity.initView(HiltDemoActivity.kt:70)
        at com.tory.library.base.BaseActivity.onCreate(BaseActivity.kt:30)
        at com.tory.demo.jetpack.Hilt_HiltDemoActivity.onCreate(Hilt_HiltDemoActivity.java:29)
        at android.app.Activity.performCreate(Activity.java:7894)
        at android.app.Activity.performCreate(Activity.java:7881)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3283)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3457) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044) 
        at android.os.Handler.dispatchMessage(Handler.java:107) 
        at android.os.Looper.loop(Looper.java:224) 
        at android.app.ActivityThread.main(ActivityThread.java:7560) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

这个报错是表示这个LiveData相同的Observer关联了不同的Lifecycle, 那这里的Observer为什么会是相同的对象呢, 如果是java,这肯定是匿名内部类,每次都是一个新的对象才对。为找到原因,我们把kotlin反编译成java可以看一下,成了如下这个样子

 LiveEventBus.get().of(HitEvent.class).observe((LifecycleOwner)this, (Observer)null.INSTANCE);

可以看到,Observer被优化成了一个常量INSTANCE!!!。但是如果我们在Observer中加一段有关外部类的内部,就不会出问题了,如果

LiveEventBus.get()
    .of(HitEvent::class.java)
    .observe(this, Observer {
        LogUtils.d("LiveEventBus HiltEvent receive: $it $this")
    })

反编译成如下样子

LiveEventBus.get().of(HitEvent.class).observe((LifecycleOwner)this, (Observer)(new Observer() {
         // $FF: synthetic method
         // $FF: bridge method
         public void onChanged(Object var1) {
            this.onChanged((HitEvent)var1);
         }

         public final void onChanged(HitEvent it) {
            LogUtils.d("LiveEventBus HiltEvent receive: " + it + ' ' + HiltDemoActivity.this);
         }
      }));

说到底,这是由于kotlin对匿名内部类的优化造成的,与外部类无关的匿名内部类会被优化成常量。

在View或者ViewHolder中监听

在View中,我们一般是拿不到LifecyclerOwner对应的,而且有进View被移除了就需要我们把监听移除,这里也提供了方法,例如:

PageEventBus.get(context)
        .of(AddressSelectEvent::class.java)
        .observe(this, Observer {
            currentAddressId = it.addressId
            notifyRefresh()
        })

这里observe的this指的是View本身,它会在onViewAttachedToWindow时真正注册,onViewDetachedFromWindow时解注册

结语

重复造轮子可以说是我们每个开发的比经之路,为什么要重复造轮子,现成的库它不香吗?一方面我们的需求会千奇百怪,自己的轮子可以更好的服务我们的需求,另一方面也可以加深自己对某方面的理解,学会怎么造一个好用的轮子。


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/31295.html

消息  监听  粘性  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1