本来想学习下EventBus最新框架的源码,但是最新的框架的代码量已经很大了,很多都是锦上添花的东西,很多核心的原理代码需要从中剥离出来去了解。但是对于刚开始看源码就直接拿到这么功能丰富并完善的代码,可能收效甚微。为了自己学习并且帮助读者同志们学习,这里自己根据以前学习的经验理解,手写一份简化Eventbus源码,和大家一起学习此框架的要义。 Eventbus的存在是为了解决组件间的简单通信问题,采用发布-订阅者模式,通过统一的管理机构(bus总线)维护所有的订阅者,将发布者发布的消息发送对应订阅者的处理方法处理。 此架构图为EventBus原版的架构图。 要手写框架,即需要包括框架核心原理和实现核心方法。这里进行列举,以明确程序框架。根据上节EventBus简介的使用方法可知需要实现: 菜谱有了,那就可以开始做菜了。如果想同时看着源码看文章的话,可以从github查看Demo。https://github.com/qingdaofu1/ZephyrBus 线程模型肯定包括默认值POSTING,即和发送线程保持一致。MAIN线程即主线程,BACKGROUND即后台线程,非主线程。这里仅作示例,可以学习EventBus增加其他选项进行串行队列的处理或者线程池多线程等特性。 创建MyEventBus类和必要方法 为了保存订阅者的处理方法需要两个数据结构。 MethodInvokeStrategy为抽象方法类,定义了收集订阅方法和调用方法的接口,并创建了主线程的Handler和工作线程Handler供调用时使用。 这里的必要方法包括单例方法getDefault、注册方法register、解注册方法unregister、发送方法post。 这是较常用写法,如果想写不一样的单例可以参考: 解注册就比较简单了,直接将集合中对应的注册类的key去除。 post方法 到这里基本所有的方法都定义好了,理解上我们可以把整个EventBus的架构原理能够理解透了。但是实际的反射策略还没有讲。 最后便是根据线程模型调用方法的实现了,这里首先想想上边说的抽象类的两个handler,有主线程的和工作线程的handler。这里可以根据注解的值ThreadMode进行对应处理。相信大家对Handler也比较认识,如果需要进一步理解Handler和线程的关系,可以查看:Handler通信机制源码解读 首先在测试module中添加发送Event源码,btn_send1为主线程发送,btn_send2会从主线程和线程中交替发送。而处理端则在注解Subscribe中分别使用默认线程和主线程。 测试结果: 纸上得来终觉浅,绝知此事要躬行。
前言
EventBus技术架构
简介
发布者和订阅者可以是任意Activity、Fragment、Service、Thread等,只要在一个进程中就可以,其不支持多进程。目前git地址为:https://github.com/greenrobot/EventBus。截至今日,最新版本3.2.0。
使用方法:EventBus in 3 steps // 1.Define events: 定义Event,这里为用户使用时自定义的部分,框架不包含此格式,为任意java bean包含自己需要传输的信息。 public static class MessageEvent { /* Additional fields if needed */ } //2 Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode: //准备订阅者方法,方法名是任意的,只是必须带@Subscribe注解,必须只有一个参数,参数需是第一步发送的消息。 @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) {/* Do something */}; //3.Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle: // 注册和解注册eventbus,可以在activity和fragment、或者线程中,根据场景需要的时机进行注册解注册。 @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } //4.Post events: 发送event,可以是任何地方 EventBus.getDefault().post(new MessageEvent());
架构原理
中间的EventBus是个大管家,负责所有消息流转和注册信息保存,程序运行起来时,注册者如Activity调用了register方法并把自己的this作为参数传入,大管家会登记所有的登记者和接收处理方法(@Subscribe注解方法)。当有发布者发布消息,即调用了EventBus.getDefault().post方法,将参数自定义Event发布到管家EventBus中, 大管家找到这个进程大家庭中所有注册了此Event类型的方法,并进行调用处理。
手写框架
模块
源码设计
自定义注解和创建ThreadMode枚举类
自定义注解可以添加默认值,并且这里标注此注解是使用在方法上,并且是运行时保留策略,关于注解的详细原理可以参照我的另一篇总结:Android注解-看这篇文章就够了@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Subscribe { ThreadMode threadMode() default ThreadMode.POSTING; int priority() default 0; }
public enum ThreadMode { POSTING, MAIN, BACKGROUD }
由于用户不一定在一个地方注册,即如果进程中Activity中、Fragment、Service等都有注册,则需要一个结合记录每个注册类中所有的订阅方法,这个集合即typesBySubscriber 。
然后比如用户有多种Event类型,比如Aevent、Bevent…,需要有个能记录所有处理Aevent的订阅者方法类和所有处理Bevent的方法类。如果有更多则记录更多。这个数据结构即subscriptionsByEventType ;其中的CopyOnWriteArrayList数据结构用法可以参考:CopyOnWriteArrayList的原理及使用
这里学习了源码中的数据结构名,以便读者返回读源码时更容易理解。/** * 同一类型EventType类与所有注册方法的集合 */ private Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType = new HashMap<>(); /** * 所有类有注册MyEventBus的 类与其内部所有处理方法集合。 */ private static final Map<Object, List<Class<?>>> typesBySubscriber = new HashMap<>(); //订阅处理方法收集和调用策略抽象类,目前只实现了反射策略,另增加了APT类,待后边完善 MethodInvokeStrategy invokeStrategy;
public abstract class MethodInvokeStrategy { private static final String TAG = "MethodInvokeStrategy"; private static HandlerThread handlerThread = new HandlerThread("workThread"); protected static Handler mainHandler; protected static Handler workHander; public MethodInvokeStrategy() { handlerThread.start(); mainHandler = new Handler(Looper.getMainLooper()); workHander = new Handler(handlerThread.getLooper()); } public List<SubscribedMethod> getAllSubscribedMethods(Object subscriber) { return null; } public void invokeMethod(Subscription subscription, Object event) { }
设计模式之单例模式的几种写法/** * 单例 * * @return */ public static MyEventBus getDefault() { if (instance == null) { synchronized (MyEventBus.class) { if (instance == null) { instance = new MyEventBus(); } } } return instance; }
根据上边描述的,为了能把发布者的Event消息发布到所有的订阅者,需要记录所有处理Event的消息方法类信息。这里包含实际的处理方法,最终返回上一步的存储集合。
由于register将当前类的this传入,即相当于获取了这个类的实例,即能够拿到其内的所有方法参数等信息。这些都是供实际收集方法的类需要的。 /** * 注册subscriber到MyEventBus,并获取其所有加了{@link Subscribe} 的方法,并放入集合中 * * @param subscriber 订阅者类,即通过register将this参数传过来的类,可以是activity、service、fragment、thread等。 */ public void register(Object subscriber) { List<SubscribedMethod> allSubscribedMethods = invokeStrategy.getAllSubscribedMethods(subscriber); for (SubscribedMethod subscribedMethod : allSubscribedMethods) { Class<?> eventType = subscribedMethod.getEventType(); CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } // TODO: 2020/4/18 如果做priority逻辑,则在此处需要排序添加 subscriptions.add(new Subscription(subscriber, subscribedMethod)); // 获取这个订阅者类中记录的所有的eventType类型 List<Class<?>> eventTypesInSubscriber = typesBySubscriber.get(subscriber); if (eventTypesInSubscriber == null) { eventTypesInSubscriber = new ArrayList<>(); typesBySubscriber.put(subscriber, eventTypesInSubscriber); } eventTypesInSubscriber.add(eventType); } printTypesBySubscriber(typesBySubscriber, subscriber); }
/** * 解注册eventbus * * @param subscriber */ public void unregister(Object subscriber) { if (typesBySubscriber != null) { typesBySubscriber.remove(subscriber); } }
post方法将参数event发送,发送时根据eventbus中大管家保存的所有能处理此event的方法发送过去处理。/** * 发送event消息到订阅者 处理方法 * * @param event */ public void post(Object event) { if (subscriptionsByEventType.size() <= 0) { Log.e(TAG, "post: no any eventbus registed named" + event.toString()); return; } CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(event.getClass()); for (Subscription subscription : subscriptions) { invokeStrategy.invokeMethod(subscription, event); } }
实现获取所有订阅者处理方法,这里既然是反射实现的,自然需要懂反射的原理,这里我在写的时候也是很多的不熟悉,后边会跟上一篇介绍反射的文章,听别人讲或者看别人文章不如自己去实现下,你会发现写的时候好像不是那么回事,还是有些不理解的。
总之核心思想就是,通过注册类,可以获取所有的方法,然后获取方法的注解,获取注解的值,获取带特定注解方法的Event类,这些都是大管家需要收集的资料。/** * 获取这个订阅者类中所有的带{@link Subscribe}的方法 * * @param subscriber 订阅者类,即通过register将this参数传过来的类,可以是activity、service、fragment、thread等。 */ @Override public List<SubscribedMethod> getAllSubscribedMethods(Object subscriber) { //记录订阅者方法参数 //CopyOnWriteArrayList<Subscription> subscribedMethods = new CopyOnWriteArrayList<Subscription>(); List<SubscribedMethod> subscribedMethods = new ArrayList<>(); Class<?> aClass = subscriber.getClass(); //获取所有方法 Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { if (declaredMethod.isAnnotationPresent(Subscribe.class)) { Class<?>[] parameterTypes = declaredMethod.getParameterTypes(); if (parameterTypes == null || parameterTypes.length > 1) { throw new IllegalArgumentException("参数不能为空,且只能有一个参数"); } Class<?> parameterType = parameterTypes[0]; Log.d(TAG, "getAllSubscribedMethods: parameterType=" + parameterType.getName()); Subscribe annotation = declaredMethod.getAnnotation(Subscribe.class); int priority = annotation.priority(); ThreadMode threadMode = annotation.threadMode(); SubscribedMethod subscribedMethod = new SubscribedMethod(declaredMethod, parameterType, threadMode, priority); Log.d(TAG, "getAllSubscribedMethods: subscribedMethod=" + subscribedMethod.toString()); subscribedMethods.add(subscribedMethod); } } return subscribedMethods; }
public void invokeMethod(Subscription subscription, final Object event) { final Object subscriber = subscription.getSubscriber(); SubscribedMethod subscribedMethod = subscription.getSubscribedMethod(); final Method method = subscribedMethod.getMethod(); switch (subscribedMethod.getThreadMode()){ case POSTING: Log.d(TAG, "invokeMethod: ThreadMode=POSTING"); invoke(method, subscriber, event); break; case MAIN: Log.d(TAG, "invokeMethod: ThreadMode=MAIN"); mainHandler.post(new Runnable() { @Override public void run() { invoke(method, subscriber, event); } }); break; case BACKGROUD: Log.d(TAG, "invokeMethod: ThreadMode=BACKGROUND"); workHander.post(new Runnable() { @Override public void run() { invoke(method, subscriber, event); } }); break; } } private void invoke(Method method, Object subscriber, Object event){ try { method.invoke(subscriber, event); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
源码测试
findViewById(R.id.btn_send1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MyEventBus.getDefault().post(new WorkEvent(5)); } }); findViewById(R.id.btn_send2).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isThreadMain) { MyEventBus.getDefault().post(new ViewEvent("主线程测试文字")); } else { new Thread() { @Override public void run() { super.run(); MyEventBus.getDefault().post(new ViewEvent("子线程测试文字")); } }.start(); } isThreadMain = !isThreadMain; } }); @Subscribe() public void onEvent(final WorkEvent event) { //...省略 } @Subscribe(threadMode = ThreadMode.MAIN) public void handleView(final ViewEvent event) { //...省略 }); }
本Demo的github地址为:https://github.com/qingdaofu1/ZephyrBus,希望有需要的同学可以下载下来实操下加深理解。
下一步计划是要增加apt模式的策略,有需要的可以继续关注。
喜欢的可以点个赞鼓励下,有问题可以留言沟通。
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算