zoukankan      html  css  js  c++  java
  • EventBus架构设计剖析

    对于EventBus这个开源框架如今基本上每个项目都会用到了,确实是在各组件之间传递消息非常之方便,官方的github地址为https://github.com/greenrobot/EventBus ,但是对其原理一直木去研究过,所以接下来则为搞清楚它的原理而努力。

    EventBus日常使用:

    对于它的使用其实都比较熟了,不过这里还是从基础使用开始,因为之后会动手自己来实现同样的效果,得循序渐进,目前官方最新的版本为:

    这里就以最新版本来进行学习研究,先添加EventBus的依赖至工程:

    好,接下来则在项目中需要接收的Activity中进行注册:

    然后咱们在第二个Activity中来发消息给第一个MainActivity,先定义一个咱们自己的消息实体类:

    好,咱们在MainActivity中定义一个用来接收EventBus发来消息的方法:

    其中它的布局文件很简单:

    好,接下来弄第二个Activity,然后在它里面进行EventBus消息的发送:

    好,接下来运行看一下效果:

    使用比较简单,其中在接收消息的方法注解中指定了UI线程来处理,那如果改为非UI呢?

     

    此时Toast抛异常了,没法更新UI,好,还是改回到UI线程处理消息,那如果咱们在发送消息的时候是在子线程呢?

    结果是木问题,毕境显示指定处理消息是在UI线程嘛,好,关于它的简单使用就到这,接下来则剖析一下它的实现原理。

    EventBus原理剖析:

    register():

    首先从注册流程分析起:

    从这个方法的名称就可以大概猜到它应该是通过反射来查询类中带有定位注解的方法,那点进去看一下该方法的细节:

    好,继续回到注流程来分析:

    关于主流程大概分析到这,这里用图来表示一下整个的主流程:

    post():

    好,接下来再来分析一下发送消息以及处理消息的流程:

     

    原来是一个ThreadLocal,很明显是跟线程相关的,也就是用它来解决多线程共享数据的问题,所继续:

    跟进去看一下细节:

    看到此处,是否能够猜出整个EventBus从发出到处理消息的一个大致的过程,反正我大概是知道了,下面大概先猜一下,之后再用源码对猜想进行进一步验证:

     

    此时发送的消息是Student类型的,所以最终EventBus框架会根据这个类型通过反射从之前注册带有@Subscribe注解的所有方法中来查找能匹配这个类型的订阅方法,最后再通过反射来调用此方法来达到消息处理的过程,那到底是不是所猜想的这样呢?下面进一步分析:

     

    咱们以MAIN的为例进行分析,其它模式就不具体分析了:

     

    看一下此方法的调用细节:

    接下来再回上一步再分析假如我们发送时是在子线程的情况处理:

    这个里面细节看都不用看,肯定用到了Handler来进行线程的切换,不信,解剖一下它里面的细节:

    而它的具体实现有几个:

     

    此时就需要验证一下mainThreadPoster是否是HandlerPoster了,下面来验证一下:

    mainThreadPoster是在EventBus的构造时创建的,而它又是由mainThreadSupport来进行创建,所以接下来则要看mainThreadSupport是啥?

    所以还得进一步分析一下"new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull)":

     

    看到木有,也就是:

    那回到主流程来:

    以上关于Post也简单用一个流程图来表示一下:

    unregister(): 

    最后就是注销流程,比较简单,稍加瞅一下细节:

     

    手写核心EventBus:

    register():

    上面对于EventBus核心原理已经搞清楚了,下面准备用纯手工的方式来根据上面分析的原理实现一下整个EventBus的核心流程,这样我想对于该框架才会有一个质的认识,所以下面开启撸码环节:

    然后此时咱们将所有用的原EventBus的代码手动替成咱们既将要实现的这个MyEventBus:

     

    好,架子都改造好了,只待具体来实现了,下面集中精力来一点点实现咱们自己的逻辑,先来处理注册方法:

    首先肯定得要获取对象的Class对象,因为最终要通过反射来获取所有的订阅方法嘛,那。。如上面看的原理所示,这里面肯定得要定义一个缓存来提高性能,所以:

    因为我们对于订阅方法还有一个MODE嘛:

    所以很明显这里面需要做一下封装:

    关于它里面的元素待下面写到时再来填充,这里先空白着,继续往下写:

    好,接下来继续回到咱们的注册方法继续往下实现:

    接下来实现这个查找方法:

    此时有可能会遍历到了Android的系统类,此时是需要过滤掉的,只需要遍历咱们自己写的就成了:

    好,接下来则需要获取类中所有的方法进行查找了:

    接下来则需要过滤标有指定注解的方法了,所以此时需要建一个Annotation,如下:

    这里面肯定还要有一个线程模式,不过这个在之后时再填充,然后用咱们自己的注解来替换EventBus的,如下:

    好,回到咱们方法的遍历中继续:

    接下来咱们得判断方法的参数只能是一个,多于1个的是不合法的,如下:

    所以判断一下:

    好,接下来则需要给注解增加一个线程切换的属性,所以咱们新建一个线程切换属性的枚举类:

    然后咱们修改一下注解:

    好,此时咱们来解析一下:

    此时MySubscriberMethod类中还没有成员信息,咱们此时得填充一下:

    好,此时就可以来填充一下方法信息了:

    至此关于注册的细节就实现完了。

    unregister():

    接下来则来实现一个注销方法,比较简单:

    post():

    接下来则来实现比较核心的发送方法了,看好了:

     

    好,接下来则来调用一下:

    好,至此大致的手动实现代码就已经写完了,接下来咱们来试一下,由于目前还没有加线程切换的逻辑,所以我们在发送消息时还是改到UI上,待之后实现了线程切换之后再来试放到子线程发送消息的情况:

    那么咱们来跑一下,抛异常了。。

    所以,咱们得修改一下:

    然后在注册时得添加订阅对象信息:

    然后再反射调用时,修改为:

    再运行一下就ok了,接下来就来处理线程切换的功能了,先来看一下目前的问题:

     

    发送放在子线程,运行则会报错如下:

    所以接下来处理一下,很简单的:

    此时就肯定需要用到Handler了,这里就不像EventBus封装得那么细了,干脆直接用一个Handler:

    接下来用一下它:

    此时咱们在发送消息时就可以在子线程了,接着继续来处理其它线程的情况:

    此时就需要弄一个线程池:

    然后:

    好,咱们可以试一下异步处理消息的情况:

    至此,关于EventBus的核心功能手动实现就完成了,可见它并非是用什么订阅者模式来实现的,其实就是用的反射+注解的方式。最后再贴一下整个MyEventBus实现的代码:

    /**
     * 纯手工版本的EventBus
     */
    public class MyEventBus {
    
        static volatile MyEventBus defaultInstance;
        private static final Map<Class<?>, List<MySubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
        private Handler handler;
        private ExecutorService executorService;
    
        private MyEventBus() {
            handler = new Handler(Looper.getMainLooper());
            executorService = Executors.newCachedThreadPool();
        }
    
        public static MyEventBus getDefault() {
            if (defaultInstance == null) {
                synchronized (EventBus.class) {
                    if (defaultInstance == null) {
                        defaultInstance = new MyEventBus();
                    }
                }
            }
            return defaultInstance;
        }
    
        /**
         * 注册
         */
        public void register(Object subscriber) {
            Class<?> subscriberClass = subscriber.getClass();
            List<MySubscriberMethod> mySubscriberMethods = METHOD_CACHE.get(subscriberClass);
            if (mySubscriberMethods == null) {
                //缓存木有则需要反射进行订阅方法的查找
                mySubscriberMethods = getSubscriberMethods(subscriber);
                METHOD_CACHE.put(subscriberClass, mySubscriberMethods);
            }
        }
    
        /**
         * 通过反射遍历所有带有订阅注解的方法列表
         */
        private List<MySubscriberMethod> getSubscriberMethods(Object subscriber) {
            List<MySubscriberMethod> list = new ArrayList<>();
            Class<?> subscriberClass = subscriber.getClass();
            while (subscriberClass != null) {//然后遍历它及它所有的父类
                String name = subscriberClass.getName();
                if (name.startsWith("java.") ||
                        name.startsWith("javax.") ||
                        name.startsWith("android.") ||
                        name.startsWith("androidx."))
                    break;
                Method[] declaredMethods = subscriberClass.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    MySubscribe annotation = declaredMethod.getAnnotation(MySubscribe.class);
                    if (annotation == null)
                        continue;
                    //判断一下方法的参数只能有一个
                    Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
                    if (parameterTypes == null || parameterTypes.length != 1) {
                        throw new RuntimeException("eventbus消息处理方法只能接收一个参数");
                    }
    
                    MyThreadMode myThreadMode = annotation.threadMode();
                    //再将方法信息封装一下
                    MySubscriberMethod mySubscriberMethod = new MySubscriberMethod();
                    mySubscriberMethod.setMethod(declaredMethod);
                    mySubscriberMethod.setThreadMode(myThreadMode);
                    mySubscriberMethod.setSubscriber(subscriber);
                    mySubscriberMethod.setEventType(parameterTypes[0]);
                    list.add(mySubscriberMethod);
    
                }
                subscriberClass = subscriberClass.getSuperclass();
            }
            return list;
        }
    
        /**
         * 发送消息
         */
        public void post(final Object event) {
            Set<Class<?>> classes = METHOD_CACHE.keySet();
            Iterator<Class<?>> iterator = classes.iterator();
            while (iterator.hasNext()) {
                Class<?> next = iterator.next();
                List<MySubscriberMethod> list = METHOD_CACHE.get(next);
                for (final MySubscriberMethod mySubscriberMethod : list) {
                    if (mySubscriberMethod.getEventType().isAssignableFrom(event.getClass())) {
                        switch (mySubscriberMethod.getThreadMode()) {
                            case MAIN:
                                if (Looper.myLooper() == Looper.getMainLooper()) {
                                    //如果是在主线程,则直接调用订阅方法
                                    invoke(mySubscriberMethod, event);
                                } else {
                                    handler.post(new Runnable() {
                                        @Override
                                        public void run() {
                                            invoke(mySubscriberMethod, event);
                                        }
                                    });
                                }
                                break;
                            case ASYNC:
                                if (Looper.myLooper() == Looper.getMainLooper()) {
                                    executorService.execute(new Runnable() {
                                        @Override
                                        public void run() {
                                            invoke(mySubscriberMethod, event);
                                        }
                                    });
                                } else {
                                    //已经在子线程,则直接执行既可
                                    invoke(mySubscriberMethod, event);
                                }
                                break;
                            case POSTING:
                                break;
                        }
                    }
                }
            }
        }
    
        /**
         * 调用订阅方法
         */
        private void invoke(MySubscriberMethod mySubscriberMethod, Object event) {
            try {
                mySubscriberMethod.getMethod().invoke(mySubscriberMethod.getSubscriber(), event);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 注销
         */
        public synchronized void unregister(Object subscriber) {
            Class<?> aClass = subscriber.getClass();
            List<MySubscriberMethod> list = METHOD_CACHE.get(aClass);
            if (list != null) {
                METHOD_CACHE.remove(aClass);
            }
        }
    }
  • 相关阅读:
    powershell网络钓鱼获取用户密码
    js 倒计时(转)
    TFS如何设置在客户端独占签出
    TFS 2010 配置的时候,提示TF255466错误
    浅谈Dynamic 关键字系列之一:dynamic 就是Object(转)
    js替换字符串中全部“-”
    苹果safari浏览器登陆时Cookie无法保存的问题
    IIS发布网站出现“未能加载文件或程序集“System.Data.SQLite”或它的某一个依赖项。”的解决方法
    Aspose.Cells单元格转换为数字格式
    SQL Server中GO的使用方法(转)
  • 原文地址:https://www.cnblogs.com/webor2006/p/12179301.html
Copyright © 2011-2022 走看看