2019独角兽企业重金招聘Python工程师标准>>>

本文由云+社区发表

事件总线核心逻辑的实现。

<!--more-->

EventBus的作用

Android中存在各种通信场景,如Activity之间的跳转,ActivityFragment以及其他组件之间的交互,以及在某个耗时操作(如请求网络)之后的callback回调等,互相之之间往往需要持有对方的引用,每个场景的写法也有差异,导致耦合性较高且不便维护。以ActivityFragment的通信为例,官方做法是实现一个接口,然后持有对方的引用,再强行转成接口类型,导致耦合度偏高。再以Activity的返回为例,一方需要设置setResult,而另一方需要在onActivityResult做对应处理,如果有多个返回路径,代码就会十分臃肿。而SimpleEventBus(本文最终实现的简化版事件总线)的写法如下:


public class MainActivity extends AppCompatActivity {TextView mTextView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mTextView = findViewById(R.id.tv_demo);mTextView.setText("MainActivity");mTextView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Intent intent = new Intent(MainActivity.this, SecondActivity.class);startActivity(intent);}});EventBus.getDefault().register(this);}@Subscribe(threadMode = ThreadMode.MAIN)public void onReturn(Message message) {mTextView.setText(message.mContent);}@Overrideprotected void onDestroy() {super.onDestroy();EventBus.getDefault().unregister(this);}}

来源Activity


public class SecondActivity extends AppCompatActivity {TextView mTextView;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mTextView = findViewById(R.id.tv_demo);mTextView.setText("SecondActivity,点击返回");mTextView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Message message = new Message();message.mContent = "从SecondActivity返回";EventBus.getDefault().post(message);finish();}});}}

效果如下:

似乎只是换了一种写法,但在场景愈加复杂后,EventBus能够体现出更好的解耦能力。

背景知识

主要涉及三方面的知识:

  1. 观察者模式(or 发布-订阅模式)

  2. Android消息机制

  3. Java并发编程

本文可以认为是greenrobot/EventBus这个开源库的源码阅读指南,笔者在看设计模式相关书籍的时候了解到这个库,觉得有必要实现一下核心功能以加深理解。

实现过程

EventBus的使用分三个步骤:注册监听、发送事件和取消监听,相应本文也将分这三步来实现。

注册监听

定义一个注解:


@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Subscribe {ThreadMode threadMode() default ThreadMode.POST;}

greenrobot/EventBus还支持优先级和粘性事件,这里只支持最基本的能力:区分线程,因为如更新UI的操作必须放在主线程。ThreadMode如下:


public enum ThreadMode {MAIN, // 主线程POST, // 发送消息的线程ASYNC // 新开一个线程发送}

在对象初始化的时候,使用register方法注册,该方法会解析被注册对象的所有方法,并解析声明了注解的方法(即观察者),核心代码如下:


public class EventBus {...public void register(Object subscriber) {if (subscriber == null) {return;}synchronized (this) {subscribe(subscriber);}}...private void subscribe(Object subscriber) {if (subscriber == null) {return;}// TODO 巨踏马难看的缩进Class<?> clazz = subscriber.getClass();while (clazz != null && !isSystemClass(clazz.getName())) {final Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {Subscribe annotation = method.getAnnotation(Subscribe.class);if (annotation != null) {Class<?>[] paramClassArray = method.getParameterTypes();if (paramClassArray != null && paramClassArray.length == 1) {Class<?> paramType = convertType(paramClassArray[0]);EventType eventType = new EventType(paramType);SubscriberMethod subscriberMethod = new SubscriberMethod(method, annotation.threadMode(), paramType);realSubscribe(subscriber, subscriberMethod, eventType);}}}clazz = clazz.getSuperclass();}}...private void realSubscribe(Object subscriber, SubscriberMethod method, EventType eventType) {CopyOnWriteArrayList<Subscription> subscriptions = mSubscriptionsByEventtype.get(subscriber);if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<>();}Subscription subscription = new Subscription(subscriber, method);if (subscriptions.contains(subscription)) {return;}subscriptions.add(subscription);mSubscriptionsByEventtype.put(eventType, subscriptions);}...}

执行过这些逻辑后,该对象所有的观察者方法都会被存在一个Map中,其Key是EventType,即观察事件的类型,Value是订阅了该类型事件的所有方法(即观察者)的一个列表,每个方法和对象一起封装成了一个Subscription类:


public class Subscription {public final Reference<Object> subscriber;public final SubscriberMethod subscriberMethod;public Subscription(Object subscriber, SubscriberMethod subscriberMethod) {this.subscriber = new WeakReference<>(subscriber);// EventBus3 没用弱引用?this.subscriberMethod = subscriberMethod;}@Overridepublic int hashCode() {return subscriber.hashCode() + subscriberMethod.methodString.hashCode();}@Overridepublic boolean equals(Object obj) {if (obj instanceof Subscription) {Subscription other = (Subscription) obj;return subscriber == other.subscribe&& subscriberMethod.equals(other.subscriberMethod);} else {return false;}}}

如此,便是注册监听方法的核心逻辑了。

消息发送

消息的发送代码很简单:


public class EventBus {...private EventDispatcher mEventDispatcher = new EventDispatcher();private ThreadLocal<Queue<EventType>> mThreadLocalEvents = new ThreadLocal<Queue<EventType>>() {@Overrideprotected Queue<EventType> initialValue() {return new ConcurrentLinkedQueue<>();}};...public void post(Object message) {if (message == null) {return;}mThreadLocalEvents.get().offer(new EventType(message.getClass()));mEventDispatcher.dispatchEvents(message);}...}

比较复杂一点的是需要根据注解声明的线程模式在对应的线程进行发布:


public class EventBus {...private class EventDispatcher {private IEventHandler mMainEventHandler = new MainEventHandler();private IEventHandler mPostEventHandler = new DefaultEventHandler();private IEventHandler mAsyncEventHandler = new AsyncEventHandler();void dispatchEvents(Object message) {Queue<EventType> eventQueue = mThreadLocalEvents.get();while (eventQueue.size() > 0) {handleEvent(eventQueue.poll(), message);}}private void handleEvent(EventType eventType, Object message) {List<Subscription> subscriptions = mSubscriptionsByEventtype.get(eventType);if (subscriptions == null) {return;}for (Subscription subscription : subscriptions) {IEventHandler eventHandler =  getEventHandler(subscription.subscriberMethod.threadMode);eventHandler.handleEvent(subscription, message);}}private IEventHandler getEventHandler(ThreadMode mode) {if (mode == ThreadMode.ASYNC) {return mAsyncEventHandler;}if (mode == ThreadMode.POST) {return mPostEventHandler;}return mMainEventHandler;}}// end of the class...}

三种线程模式分别如下,DefaultEventHandler(在发布线程执行观察者放方法):


public class DefaultEventHandler implements IEventHandler {@Overridepublic void handleEvent(Subscription subscription, Object message) {if (subscription == null || subscription.subscriber.get() == null) {return;}try {subscription.subscriberMethod.method.invoke(subscription.subscriber.get(), message);} catch (IllegalAccessException | InvocationTargetException e) {e.printStackTrace();}}}

MainEventHandler(在主线程执行):


public class MainEventHandler implements IEventHandler {private Handler mMainHandler = new Handler(Looper.getMainLooper());DefaultEventHandler hanlder = new DefaultEventHandler();@Overridepublic void handleEvent(final Subscription subscription, final Object message) {mMainHandler.post(new Runnable() {@Overridepublic void run() {hanlder.handleEvent(subscription, message);}});}}

AsyncEventHandler(新开一个线程执行):


public class AsyncEventHandler implements IEventHandler {private DispatcherThread mDispatcherThread;private IEventHandler mEventHandler = new DefaultEventHandler();public AsyncEventHandler() {mDispatcherThread = new DispatcherThread(AsyncEventHandler.class.getSimpleName());mDispatcherThread.start();}@Overridepublic void handleEvent(final Subscription subscription, final Object message) {mDispatcherThread.post(new Runnable() {@Overridepublic void run() {mEventHandler.handleEvent(subscription, message);}});}private class DispatcherThread extends HandlerThread {// 关联了AsyncExecutor消息队列的HandleHandler mAsyncHandler;DispatcherThread(String name) {super(name);}public void post(Runnable runnable) {if (mAsyncHandler == null) {throw new NullPointerException("mAsyncHandler == null, please call start() first.");}mAsyncHandler.post(runnable);}@Overridepublic synchronized void start() {super.start();mAsyncHandler = new Handler(this.getLooper());}}}

以上便是发布消息的代码。

注销监听

最后一个对象被销毁还要注销监听,否则容易导致内存泄露,目前SimpleEventBus用的是WeakReference,能够通过GC自动回收,但不知道greenrobot/EventBus为什么没这样实现,待研究。注销监听其实就是遍历Map,拿掉该对象的订阅即可:


public class EventBus {...public void unregister(Object subscriber) {if (subscriber == null) {return;}Iterator<CopyOnWriteArrayList<Subscription>> iterator = mSubscriptionsByEventtype.values().iterator();while (iterator.hasNext()) {CopyOnWriteArrayList<Subscription> subscriptions = iterator.next();if (subscriptions != null) {List<Subscription> foundSubscriptions = new LinkedList<>();for (Subscription subscription : subscriptions) {Object cacheObject = subscription.subscriber.get();if (cacheObject == null || cacheObject.equals(subscriber)) {foundSubscriptions.add(subscription);}}subscriptions.removeAll(foundSubscriptions);}// 如果针对某个Event的订阅者数量为空了,那么需要从map中清除if (subscriptions == null || subscriptions.size() == 0) {iterator.remove();}}}...}

以上便是事件总线最核心部分的代码实现,完整代码见vimerzhao/SimpleEventBus,后面发现问题更新或者进行升级也只会改动仓库的代码。

局限性

由于时间关系,目前只研究了EventBus的核心部分,还有几个值得深入研究的点,在此记录一下,也欢迎路过的大牛指点一二。

性能问题

实际使用时,注解和反射会导致性能问题,但EventBus3已经通过Subscriber Index基本解决了这一问题,实现也非常有意思,是通过注解处理器(Annotation Processor)把耗时的逻辑从运行期提前到了编译期,通过编译期生成的索引来给运行期提速,这也是这个名字的由来。

可用性问题

如果订阅者很多会不会影响体验,毕竟原始的方法是点对点的消息传递,不会有这种问题,如果部分订阅者尚未初始化怎么办。等等。目前EventBus3提供了优先级和粘性事件的属性来进一步满足开发需求。但是否彻底解决问题了还有待验证。

跨进程问题

EventBus是进程内的消息管理机制,并且从开源社区的反馈来看这个项目是非常成功的,但稍微有点体量的APP都做了进程拆分,所以是否有必要支持多进程,能否在保证性能的情况下提供同等的代码解耦能力,也值得继续挖掘。目前有lsxiao/Apollo和Xiaofei-it/HermesEventBus等可供参考。

参考

  • greenrobot/EventBus

  • hehonghui/AndroidEventBus

  • 《Android开发艺术探索》第十章

此文已由作者授权腾讯云+社区发布


转载于:https://my.oschina.net/qcloudcommunity/blog/2995084

自己动手写事件总线(EventBus)相关推荐

  1. vue中央事件总线eventBus的简单理解和使用

    公共事件总线eventBus的实质就是创建一个vue实例,通过一个空的vue实例作为桥梁实现vue组件间的通信.它是实现非父子组件通信的一种解决方案. 用法如下: 第一步:项目中创建一个js文件(我通 ...

  2. vue 事件总线EventBus的概念、使用以及注意点

    vue组件中的数据传递最最常见的就是父子组件之间的传递.父传子通过props向下传递数据给子组件:子传父通过$emit发送事件,并携带数据给父组件.而有时两个组件之间毫无关系,或者他们之间的结构复杂, ...

  3. Google guava 事件总线 EventBus 进程内消息队列

    Google guava 事件总线 EventBus 创建事件总线流程 码代码 引入依赖 一个简单的事件处理 监听者 创建事件生产者总线.注册事件监听者.发送事件 运行结果 扩展 多个事件监听者加De ...

  4. 200代码写一套属于自己的事件总线(EventBus)库

    理论千万篇,不如实战来一篇. 源码 https://github.com/harvie1208/EventBus 关键词:观察者模式.反射.自定义注解.线程调度 手写200行代码,一步一步实现Even ...

  5. Android之事件总线EventBus详解

    顾名思义,AndroidEventBus是一个Android平台的事件总线框架,它简化了Activity.Fragment.Service等组件之间的交互,很大程度上降低了它们之间的耦合,使我们的代码 ...

  6. Android事件总线——EventBus的使用

    前言 首先我们来说下事件总线,它的作用:为了更简化并更高质量的在Activity,Fragment,Thread和Service等之间的通信,解决组件之间高耦合的同时仍能进行高效的通信. 什么是Eve ...

  7. 2021-12-14 vue移动端卖座电影项目(十二) 使用mapState控制封装选项卡tabbar的显隐,以及回顾使用中央事件总线eventbus和vuex的state控制tabbar显隐的异同

    文章目录 0.实现场景:进入详情页时,底部选项卡隐藏 1.使用中央事件总线控制tabbar的v-show的值 2.使用vuex的state控制tabbar的v-show的值 3.使用vuex的muta ...

  8. Android 事件总线 EventBus入门及使用

    何为事件总线? 是对发布-订阅模式的一种实现. 它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的.学习它之前最好先了解下观察者模式 说白了就是不同组件间 ...

  9. 安卓Android事件总线EventBus简单使用

    EventBus是的主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息,优点是开销小,代码更优雅.以及将发送者和接收者 ...

最新文章

  1. R语言使用unzip函数解压压缩文件(Extract or List Zip Archives)
  2. docker volume(数据卷)是什么?(就是在宿主机上挂载的共享文件目录)
  3. 清空SqlServer日志文件
  4. Android Studio安装genymotion模拟器
  5. javascript sort()实现元素json对象的排序
  6. 新的学期、新的开始、新的付出、新的收获!
  7. 语言把数据写入csv文件_把JSON/CSV文件打造成MySQL数据库
  8. python3.7安装tensorflow-gpu_tensorflow-gpu安装的常见问题及解决方案
  9. PossibleSums
  10. Go多线程并发避免堵塞
  11. Android实战开发通用流行框架大全
  12. 绝对靠谱安全的论文免费安全查重检测重复率网站
  13. 怎么查看XP系统是32位还是64位
  14. 计算机考研人工智能方向,2022考研计算机学科研究方向:人工智能
  15. 熊猫烧香、威金的解决办法
  16. 小程序之100推荐:901~1000
  17. 摩羯座 计算机专业,为什么说摩羯座是一个很“作”的星座?
  18. 某校2019专硕编程题-前10名学生成绩
  19. icpc 昆明 A.AC
  20. 有什么软件android跟电脑连接无线,手机电脑连接的软件有哪些(安卓手机连接电脑的方法)...

热门文章

  1. Python中的append()和extend()
  2. Java Number shortValue()方法与示例
  3. short 用equals_Java Short类equals()方法的示例
  4. JavaScript中的String()函数与示例
  5. 面向对象(封装对象private关键字)
  6. java const string_深入研究Java String
  7. 进程创建fork-小代码
  8. 92. 反转链表 II golang
  9. [Linux]消息队列
  10. linux 安装qt 4.6软件,QT学习之一:Linux下安装QT之版本qt-4.6.3