EventBus源码分析以及简单的手动实现

文章主要内容为:

1.EventBus源码分析
2.手动实现一个简单版的EventBus

本文不再介绍EventBus的用法

  • 首先来看看EventBus的入口
public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

这里使用单例模式来创建EventBus实例,同时加锁,保证在不同线程中,EventBus的唯一

  • 在看看EventBus注册的方法
public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

前两行代码的主要是先获取到订阅类的class,然后将class里面的methods存放到List<SubscriberMethod>,我们在这里主要看一下第二行代码的findSubscriberMethods()方法

  • findSubscriberMethods()
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

首先会在“METHOD_CACHE”这个数组里面查找是否有缓存过的method,ignoreGeneratedIndex默认为false,现在再往下面看看findUsingInfo()方法

  • 比较关键的findUsingInfo()
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

这里用了FindState这个内部类来用于保存订阅者类的信息,以map的形式保存;接下来用initForSubscriber()这个方法进行初始化;然后再findUsingReflectionInSingleClass()在这个方法内,会使用反射,将你所有含有注解的方法从class里面遍历出来,并且保存在FindState的list当中,对于有注解但是不符合要求的,会通过异常抛出

  • 看看最后一排的getMethodsAndRelease()方法
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
        List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
        findState.recycle();
        synchronized (FIND_STATE_POOL) {
            for (int i = 0; i < POOL_SIZE; i++) {
                if (FIND_STATE_POOL[i] == null) {
                    FIND_STATE_POOL[i] = findState;
                    break;
                }
            }
        }
        return subscriberMethods;
    }

getMethodsAndRelease()主要做的事情就是将findstate里面的method取出来;然后将findstate里面的map释放掉;最后将自己的引用赋给一个空FIND_STATE_POOL[i],起到复用池的效果

  • 在regisert的最后,会调用subscribe()方法,这个方法主要是订阅的方法与事件进行注册

  • 发送事件 post

public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

PostingThreadState这个类主要是保存当前线程的信息,订阅者和订阅事件。这里post的流程是先从currentPostingThreadState获取当前线程信息,然后将当前线程的事件取出来,然后添加到队列里面等待分发;这里在循环队列中的事件的时候,也会循环当前事件的父类和接口,查找出所有的订阅者;最后会在postToSubscription()方法里面判断在那个线程里面执行方法,通过反射执行符合需求的包含注解的方法

好的,我们接下来用100多行代码 ,来实现eventbus的事件发送

-先写一个person对象

public class Person {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    private String name;
    private String sex;

    public Person(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
}
  • 然后在写一个注解接口类
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.PostThread;
}
  • 在写一个SubscriberMethod对象,里面用来存放订阅者的一些信息
public class SubscribleMethod {
    //订阅事件的method对象
    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 ThreadMode getThreadMode() {
        return threadMode;
    }

    public Class<?> getEventType() {
        return eventType;
    }
}

-在写一个枚举类,用于存放线程位置的标志,ThreadMode

public enum ThreadMode {

    PostThread,

    MainThread,

    BackgroundThread,

    Async
}
  • 好了 接下来开始写eventbus的注册和发送事件
public class Eventbus {
    private static Eventbus instance = new Eventbus();

    public static Eventbus getDefault() {
        return instance;
    }

    //这个map用于保存订阅者的信息
    private Map<Object, List<SubscribleMethod>> cacheMap;

    private Eventbus() {
        this.cacheMap = new HashMap<>();
    }


    public void register(Object activity) {
        Class<?> clazz = activity.getClass();
        List<SubscribleMethod> list = cacheMap.get(activity);
        //判断是否注册过
        if (list == null) {
            list = getSubscribleMethods(activity);
            cacheMap.put(activity, list);
        }
    }
    
    //获取activity下所有的订阅方法
    private List<SubscribleMethod> getSubscribleMethods(Object activity) {
        List<SubscribleMethod> list = new ArrayList<>();
        Class clazz = activity.getClass();
        while (clazz != null) {
            String name = clazz.getName();
            //如果包名是java javax android开头的 说明是系统的方法 直接跳过 节省事件
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                break;
            }
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                Subscribe subscribe = method.getAnnotation(Subscribe.class);
                if (subscribe == null) {
                    continue;
                }
                //event只能接受一个参数
                Class[] paratems = method.getParameterTypes();
                if (paratems.length != 1) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + paratems.length);
                }
                //满足所有要求 找到所有的订阅方法 全部return出去
                ThreadMode threadMode = subscribe.threadMode();
                SubscribleMethod subscribleMethod = new SubscribleMethod(method
                        , threadMode, paratems[0]);
                list.add(subscribleMethod);

            }
            clazz = clazz.getSuperclass();
        }
        return list;
    }

    public void post(final Object object) {
        Set<Object> set = cacheMap.keySet();
        Iterator iterator = set.iterator();
        //遍历caheMap表中所有的订阅者
        while (iterator.hasNext()) {
            //获取到actiivty对象
            final Object activity = iterator.next();
            //获取activity下存放订阅者信息的list
            List<SubscribleMethod> list = cacheMap.get(activity);
            //遍历所有订阅者
            for (final SubscribleMethod subscribleMethod : list) {
                //判断订阅类型是不是一样的 如果是一样的就需要接受事件
                if (subscribleMethod.getEventType().isAssignableFrom(object.getClass())) {
                    invoke(subscribleMethod, activity, object);
                }
            }
        }
    }

    //反射发送事件
    private void invoke(SubscribleMethod subscribleMethod, Object activity, Object object) {
        Method method = subscribleMethod.getMethod();
        try {
            method.invoke(activity, object);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
  • 好了,所有代码写完了,现在在activity里面使用就行
//注册
Eventbus.getDefault().register(this);

//发送消息
Eventbus.getDefault().post(new Person("abc", "男"));

//接受消息
    @Subscribe(threadMode = ThreadMode.MainThread)
    public void receive(Person person ) {
        Toast.makeText(this, person.getName()+"----"+person.getSex(), Toast.LENGTH_SHORT).show();
    }

如果想要加上线程判断的话,在post方法里面通过subscribleMethod.getThreadMode()进行判断就行

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容