手把手带你造轮子-自己动手实现一款EventBus框架

一、前言:

EventBus是一款事件发布与订阅的框架,主要用来替代Intent、Handler、BroadCast来实现组件通信,线程间通信。优点是:开销小,代码优雅,以及将发送者与接收者解耦,使用非常简便。分析EventBus的博客很多,但是很少有带大家去实现一款EventBus框架的。没错,这里我将带你实现一款自己的EventBus框架。不管你之前是否接触过EventBus,都可以通过学习而本篇文章,来学习EventBus。

二、准备

在看这篇博客之前需要对反射、注解有一定的认识,否则你是不容易看懂的。

三、流程

整个EventBus发布订阅的流程可以形象为以下过程,包括后面的部分都是以这个流程来分析的

1、订阅过程:

假设某公司要招聘Android、IOS、前端、Java...程序员,它会先在招聘中介网站上(比如“前程无忧”)提交这些信息。而“前程无忧”需要记下这些信息,它会将该公司招聘的所有岗位存入一个List<招聘岗位>集合中,因为招人的公司肯定不止这一家,所以然后"前程无忧"会以公司为键,招聘岗位List为值存入一个Map<公司,List<招聘岗位>>中。这就是一个订阅过程、招聘的公司就是订阅者、“前程无忧”就是一个中介。

2、发布过程:

求职者为了找工作,也会去“前程无忧”上面发布找工作的消息,假设我们找的是Android的工作,“前程无忧”就会去它的Map<公司名,List<招聘岗位>>中找,看有哪个公司是招聘Android的,找到之后就会把Android求职推荐给该公司。

3、注销过程:

假设公司不需要再招人,那么怎么办呢?那就注销呗,此时“前程无忧”就会在它的Map<公司名,List<招聘岗位>>中,将该公司移除掉。

上面的过程大概可以用下面这张图来表示:

手把手带你造轮子-自己动手实现一款EventBus框架_第1张图片
IMG20170527163016.jpg

理清思路之后我们就可以来撸码了。

四、正式撸码

1、先看下我们所写代码的整体结构:

![


手把手带你造轮子-自己动手实现一款EventBus框架_第2张图片
021IU)(QA507A[Y3K%@20KT.png](http://upload-images.jianshu.io/upload_images/4933844-54f113ac78de4734.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

没错,就这四个类就是实现了一款EventBus,麻雀虽小,但也是五脏俱全。EventBus类就是“前程无忧”,SubscribleMethod类就是招聘岗位,ThreadMode和Subscrible是啥先别管它。

2.EventBus类的基本实现(前程无忧)

求职者要找工作,公司要招人,那么肯定得先有一个招聘的招聘的中介"前程无忧"。那么我们现在就来写一个中介EventBus类,至于怎么写了?大家在使用EventBus的时候都知道使用以下这句代码进行注册(订阅):
EventBus.getDefault().regist(this);
那么我们就依葫芦画瓢,写一个getDefault()
方法,代码如下:

/**
 * Created by Administrator on 2017/5/22.
 * 招聘中介
 */
public class EventBus {
    //招聘中介的信息表(表里面填了各大公司需要招聘的岗位)
    //Map<招聘公司,该公司招聘的岗位集合>
    private Map> cacheMap;
    private static volatile EventBus instance;
    private EventBus(){
        cacheMap=new HashMap<>();
    }
    public static EventBus getDefault(){
        if(instance==null){
            synchronized (EventBus.class){
                if(instance==null){
                    instance=new EventBus();
                }
            }
        }
        return instance;
    }
}

代码行数不多,采用了一个双重检查锁式的单例模式来创建EventBus的实例。cacheMap就是用来存储各公司招聘信息的。SubscribleMethod这个类就是招聘岗位信息的封装类,暂时不用管它,咱们后面再说。

2、注册方法的实现()

有了EventBus的实例,那么我们就来实现regist方法,代码如下:

 /**
     * 注册
     * @param obj 公司
     */
    public void regist(Object obj){ 
        List list = cacheMap.get(obj);
        //还没有注册过,那么就将岗位集合put进中介的map中,
        //如果已经注册过了,那么久不用再次注册,那么什么都不做
        if(list==null){
            //找到公司obj招的所有岗位的集合
            List methods = findSubscribleMethod(obj); 
            cacheMap.put(obj,methods);
        }else {
            throw new RuntimeException("你就已经注册(订阅)过了,不需要再次注册(订阅)");
        }
    }

上面这个注册方法,参数就是要注册的公司对象。首先会去“前程无忧”的Map集合里面找一下,看是否这个公司已经注册(订阅)过了,如果已经注册(订阅)过了,那就抛一个异常告诉它不需要再注册了,如果没有注册(订阅),才让他注册(订阅),注册的公司obj需要招人,那么就得知道obj这个公司到底招聘那些岗位,findSubscribleMethod(obj)这个方法就是找到公司obj的所有招聘岗位(具体实现咱们后面再说,你现在只要知道它是干啥的就行),找到公司obj所有岗位的集合后,后面的自然是将公司招聘信息存入“前程无忧”的招聘信息表Map里面去了, 也就是这句代码cacheMap.put(obj,methods)。

2、Subscrible是个啥?

上面的代码结构图中出现Subscrible,那它是个啥呢?我们现在就来说一说,代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscrible {
    ThreadMode value() default ThreadMode.PostThread;
}

没错,它就这几行代码,灰常简单,可以看出它就是一个注解, 简单的来说,招聘的公司招人它得有个招聘的方法呀,招聘的公司肯定也得干其他的事,肯定也有非招聘的方法,那么怎么知道它的哪些方法是招聘的方法呢?那么就给它的招聘方法上面加个Subscrible注解我们就知道了,里面的value()先不管它,下面先给个示例:

 /**
     *公司招人的方法
     * @param content
     */
    @Subscrible(ThreadMode.PostThread)
    public void receive(AndroidProgrammer content){
        Log.i("zkx","Thread  "+Thread.currentThread().getName());
        //Toast弹不出来,不知道为啥
        //Toast.makeText(this,Thread.currentThread().getName(),Toast.LENGTH_SHORT);
    }

@Subscrible(ThreadMode.MainThread)这句代码就表示了这个方法是一个招聘方法,()里面的代码表示这个方法是在主线程里面执行的。该方法的参数类型就表明了招的是什么职位,我这里是招聘AndroidProgrammer,至于ThreadMode请看下面。

3、ThreadMode是个啥?

ThreadMode听名字就知道是线程模式的意思,具体的请看代码:

public enum ThreadMode {
    PostThread,//发送线程执行
    MainThread,//主线程执行
    BackgroundThread,//后台线程执行
    Async

这段其实就是直接复制EventBus源码里面的,英文注释太长了,我给去掉了。可以看出他就是一个枚举,请看我给出的注释。再回到
注解Subscrible的代码中,我们就可以知道它里面的value()值就是说加上这个注解的在哪个线程执行了,我的代码中默认指定的是PostThread。

4、SubscribleMethod是个啥?

从名字是可以看出它是订阅方法的意思,实际是上它是封装了招聘岗位信息(公司招聘的方法、招聘岗位的类型、招聘方法在哪个线程执行)的一个JavaBean。具体代码如下:

**
 * Created by Administrator on 2017/5/22.
 * 招聘岗位信息类
 */
public class SubscribleMethod {
    //招聘这个岗位的方法
    private Method method;
    //招聘这个岗位的方法在哪个线程里面执行
    private ThreadMode threadMode;
    //招聘的岗位的类型
    private Class eventType;

    public SubscribleMethod(Method method, ThreadMode threadMode, Class eventType) {
        this.method = method;
        this.threadMode = threadMode;
        this.eventType = eventType;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public ThreadMode getThreadMode() {
        return threadMode;
    }

    public void setThreadMode(ThreadMode threadMode) {
        this.threadMode = threadMode;
    }

    public Class getEventType() {
        return eventType;
    }

    public void setEventType(Class eventType) {
        this.eventType = eventType;
    }
}

5、深入细节

到这里EventBus的各个类的意思已经说完了,我们来看一些细节的东西。上面“2、注册方法的实现”这段,我们说到findSubscribleMethod(obj)这个方法就是找到公司obj的所有招聘岗位,那么我们就来看看它是怎么找的,代码如下:

 /**
     * 寻找该公司的招聘岗位
     * @param activity 该公司
     * @return
     */
    private List findSubscribleMethod(Object activity) {
        List list  = new CopyOnWriteArrayList<>();
        Class clazz = activity.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        //循环查找父类 接收方法
        while(clazz!=null){//如果该公司又隶属于某个更大的公司,那么它也要为这个更大的公司招人
            String name = clazz.getName();
            //系统方法直接break
            if(name.startsWith("java.")||name.startsWith("javax.")||name.startsWith("android.")){
                break;
            }
            //遍历所有类的方法(其中有Subscrible注解的为招人的岗位(方法))
            //每找到一个招聘岗位就将其放到集合中,最终返回的集合list就是该公司所有招聘岗位的集合
            for (Method method:methods){
                //持有Subscrible注解的才是个接收方法,才是个岗位
                //发生线程
                Subscrible subscrible = method.getAnnotation(Subscrible.class);
                if (subscrible==null){
                    continue;
                }
                //拿到方法的参数类型的数组
                Class[] parameters = method.getParameterTypes();
                if(parameters.length!=1){//只能接收一个参数
                    throw new RuntimeException("eventbus must be one parameter");
                }
                //获得参数类型数组的第一个元素(也只有一个元素)
                Class paramClass  = parameters[0];
                //拿到发生的线程(默认发生在PostThread)
                ThreadMode threadMode = subscrible.value();
                //new一个招聘岗位
                SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,paramClass);
                //招聘中介(前程无忧) 把该公司所有岗位缓存到集合
                list.add(subscribleMethod);
            }
            clazz=clazz.getSuperclass();
        }
        return list;
    }

从代码总可以看出,我们其实就是通过反射拿到该招聘公司的所有方法,遍历这些方法,判断这些方法是不是招聘的方法,具体的就是看这些方法有没有加Subscrible 注解,加了注解的肯定就是招聘方法,继而拿到,每一个招聘方法的参数类型,也就是招聘的岗位,然后将所有的岗位存入List集合中。还有一点要注意的是如果公司类是继承自某个类,也就是说该公司它是某个更大的公司的一个子公司,那么它在招人的同时也得给它的父公司招人。所以有了我们的最外层的while循环,不断遍历它的父类,直到系统类为止。

6、发布的方法

到这里,中介有了,公司也注册(订阅)了,那么就到我们求职者出场了,假设我们要找Android程序员的工作,那么就只需要调用下面这句代码就行:

EventBus.getDefault().post(new AndroidProgrammer("zkx","123456"));

其实就是调用了EventBus的post方法,具体实现如下:

 /**
     * 安卓程序员 来找工作
     * @param object 求职的岗位类型
     */
    public void post(final Object object) {
        //拿到集合cacheMap里所有的公司
        Set set = cacheMap.keySet();
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            //拿到具体的每一个公司
            final Object activity = iterator.next();
            //拿出该公司招聘的岗位集合
            List list = cacheMap.get(activity);
            //遍历该公司招聘岗位的集合,看有没有招聘安卓的
            for (final SubscribleMethod method:list) {
                //如果是属于同样的类型(如果有岗位匹配)
                if(method.getEventType().isAssignableFrom(object.getClass())){
                    //判断当前接收方法是在哪个线程
                    switch (method.getThreadMode()){
                        //发送方法的线程和接收的线程在同一个线程,直接invoke就完事了
                        case PostThread:
                            //activity:公司,method:岗位,object:求职者
                            //岗位匹配,把这个人招进来
                            invoke(activity,method,object);
                            break;
                        case MainThread:
                            //判断发送线程是在哪个线程,
                            //如果发送线程就是主线程就不用切换线程
                            if(Looper.myLooper()==Looper.getMainLooper()){
                                //直接调用
                                invoke(activity,method,object);
                            }else {//发送是在子线程
                                handler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(activity,method,object);
                                    }
                                });
                            }
                            break;
                        //指定接收方法发生在子线程
                        case BackgroundThread:
                            //如果当前发送线程就是子线程就不用切换线程
                            if(Looper.myLooper()!=Looper.getMainLooper()){
                                //发生在子线程
                                invoke(activity,method,object);
                            }else {
                                executorService.execute(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(activity,method,object);
                                    }
                                });
                            }
                            break;
                    }
                }
            }
        }
    }
 
 

代码有些长,但是基本上每行代码都给出了注释。理解起来应该不难。大体的流程就是在”前程无忧”的信息表里面拿到所有的公司,然后遍历这些公司,看这些公司的招聘岗位里面有木有Android的岗位(是否匹配),如果有的话,那就利用反射去调用有招聘Android岗位的公司的招聘方法来招聘这个求职者。还有需要注意的就是,我们会根据发布方法(求职者发布求职的方法)和接收方法(公司招人的方法)所在线程的不同,采取不同的策略。具体请看代码,有详细的注释。反射调用公司的招聘方法代码如下:


    /**
     * @param activity 公司
     * @param method   岗位信息封装类
     * @param object   求职者
     */
    private void invoke(Object activity, SubscribleMethod method, Object object) {
        try {
            //调用公司activity的岗位method的招人方法method.getMethod()方法,
            //传参object,代表招Object类型岗位的人
            method.getMethod().invoke(activity,object);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

7注销方法的实现(取消订阅)

公司不招人了,那么就得注销(取消订阅),代码很简单,就是将该公司的信息从EventBus的map中移除就行了

 public void unregist(Object object){
        //在中介的map中将该公司object移除
        cacheMap.remove(object);
 }

五、最后

到这里我们所有的代码就写完了,现在就可以使用我们写的这款框架来进行组件间、线程间通信了,写的不好的或者错误的,欢迎批评指正。
全部代码下载地址:
https://github.com/zkxok/MyEventBus

你可能感兴趣的:(手把手带你造轮子-自己动手实现一款EventBus框架)