从零开始“撸”出自己得EventBus(三)

转载文章请标明出处 http://www.jianshu.com/p/476a3a6dc789
如果你没有看过我之前的eventbus系列的文章建议先去看看

  1. 从零开始“撸”出自己的EventBus(一)
  2. 从零开始“撸”出自己的EventBus(二)

上一篇文章我们用了第三种方案实现了我们的EventBus,并且完善了线程切换功能。用过最新版的EventBus的朋友都知道,我们的EventBus还远远没有人家的好用,原因主要是

  1. 不支持注解方式配置观察者方法,定义出来的方法方法名太长很难看
  2. 不支持事件优先级配置
  3. 不支持粘性事件

今天这次呢,我们就来解决这些问题。

梦想起航

首先我们在原有工程下新建一个包big_bus,我们就在这个包下面完善我们的EventBus。
然后我们来定义我们的注解类,就抄一抄人家greenrobot的吧。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    //事件执行线程
    EventThread eventThead() default EventThread.DEFAULT;

    //是否为粘性事件
    boolean sticky() default false;

    //事件优先级
    int priority() default 0;
}

这个我相信有点注解基础的都能看懂吧?@interface就是用来定义这是个注解类的,Target是个元注解,定义我们这个注解是面向类,方法还是变量啥的,我们的Subscribe注解面向的是方法。Retention是另一个元注解,标明我们这个注解的级别是源代码级(只在代码中能看到,编译时就会被忽略),class级别(会被编译保留到class文件中,但是不能被反射读取到),运行时级别(能被反射读取),我们这里肯定是通过反射去读取的,所以就标明了为最高的级别。实在看不懂去看看Java基础关于注解方面的,很简单的。
接下来我们来定义我们的BusMethod的类(有跟着我写第二篇的应该很清楚这个类是干嘛的)。先贴代码吧。。。。

public class BusMethod implements Comparable<BusMethod> {
    private Method mMethod;
    private WeakReference<Object> mObserverRefer;
    private EventThread mEventThread;
    //是否接受粘性事件
    private boolean mSticky;
    //优先级
    private int mPriority;

    public Method getMethod() {
        return mMethod;
    }

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

    public WeakReference<Object> getObserverRefer() {
        return mObserverRefer;
    }

    public void setObserverRefer(WeakReference<Object> observerRefer) {
        this.mObserverRefer = observerRefer;
    }

    public EventThread getEventThread() {
        return mEventThread;
    }

    public void setEventThread(EventThread eventThread) {
        this.mEventThread = eventThread;
    }

    public boolean isSticky() {
        return mSticky;
    }

    public void setSticky(boolean sticky) {
        this.mSticky = sticky;
    }

    public int getPriority() {
        return mPriority;
    }

    public void setPriority(int priority) {
        this.mPriority = priority;
    }

    @Override
    public int compareTo(@NonNull BusMethod busMethod) {
        int priority = busMethod.getPriority();
        //这里不能返回0,原因稍后说明
        if (this.mPriority < priority) {
            return 1;
        } else {
            return -1;
        }
    }

    public Object getObserver() {
        Object observer = null;
        if (null != mObserverRefer) {
            observer = mObserverRefer.get();
        }
        return observer;
    }
}

这里说明一下和原来的BusMethod的区别以及原因。因为我们要在观察者注册的时候把优先级和是否是粘性事件都读取出来,所以多了这两个属性。实现Comparable是为了方便我们进行优先级排序,这里特别注意compareTo不能返回0
我数了数我们的BusMethod类,属性太多了,所以我决定用构造器模式,到时候我们书写代码的时候会比较美观。

public class BusMethodBuilder {
    private BusMethod mBusMethod = new BusMethod();

    public BusMethod create() {
        return this.mBusMethod;
    }

    public BusMethodBuilder setMethod(Method method) {
        this.mBusMethod.setMethod(method);
        return this;
    }

    public BusMethodBuilder setObserver(Object observer) {
        this.mBusMethod.setObserverRefer(new WeakReference<Object>(observer));
        return this;
    }

    public BusMethodBuilder setEventThread(EventThread eventThread) {
        this.mBusMethod.setEventThread(eventThread);
        return this;
    }

    public BusMethodBuilder setSticky(boolean sticky) {
        this.mBusMethod.setSticky(sticky);
        return this;
    }

    public BusMethodBuilder setPriority(int priority) {
        this.mBusMethod.setPriority(priority);
        return this;
    }
}

然后我们定义一个我们的异常类方便对异常进行处理。

public class EventException extends RuntimeException {
    public EventException() {
    }

    public EventException(String message) {
        super(message);
    }

    public EventException(String message, Throwable cause) {
        super(message, cause);
    }

    public EventException(Throwable cause) {
        super(cause);
    }

}

还有我们的运行线程类需要定义一个枚举

public enum EventThread {
    DEFAULT, //默认线程,即在哪个线程调用被调用方法就在哪个线程执行
    MAIN, //不管调用方法在哪个线程,被调用方法均在UI线程执行
    BACKGROUND, //若在UI线程调用则新开一个线程处理,若在非UI线程调用则在原线程处理
    ASYNC //不管调用方法在哪个线程,被调用方法均新开一个线程执行
}

冲浪~

准备工作都做好啦,我们来出来大家伙。既然是大家伙我就起名叫bigbus好了(咦~不是一开始就起好名字了吗?)
先把要暴露的方法定义出来吧,大概就是酱紫的。。。

public class BigBus {
    private static class BusHolder {
        private static BigBus instance = new BigBus();
        private static Map<Class<?>, TreeSet<BusMethod>> data = new HashMap<>();
        private static List<Object> sticks = new LinkedList<>();
    }

    public static BigBus getInstance() {
        return BusHolder.instance;
    }

    public void register(@NonNull Object observer) {}
    
    public void post(@NonNull Object event) {}
    
    public void postSticky(@NonNull Object event){}
    
    public void unregister(@NonNull Object observer){}

我们需要特别注意的是,我们把原来的List换成了TreeSet来保存BusMethod。为什么要这么做呢?因为TreeSet自带排序功能,我们就省去了排序的工作,这也是为什么前面compareTo接口方法不能返回0的原因,因为返回0的话TreeSet会认为这两个方法是同一个方法,然后后面put进去的方法就把前面的观察者方法给覆盖了。
这次我们在BusHolder里面新增了个粘性事件的集合来存储粘性事件。其实粘性事件的处理很简单。

  1. 在执行postSticky方法后将event存储到sticks中去
  2. 在有新的Observer注册的时候遍历其方法,遍历其所有支持粘性事件的方法与BusHolder里面的sticks进行比对,若参数类型一致则触发其事件。

首先来实现register方法。

public void register(@NonNull Object observer) {
        List<Method> methods = findUsefulMethods(observer);
        //如果一个有效方法都没有则抛出异常
        if (null == methods || methods.size() == 0) {
            throw new EventException("注册的观察者没有有效的观察者方法");
        }

        Iterator<Method> iterator = methods.iterator();
        while (iterator.hasNext()) {
            Method method = iterator.next();
            EventThread eventThread = findMethodThread(method);
            boolean sticky = isStickEvent(method);
            int priority = getPriority(method);

            BusMethod busMethod = new BusMethodBuilder()
                    .setMethod(method)
                    .setEventThread(eventThread)
                    .setObserver(observer)
                    .setPriority(priority)
                    .setSticky(sticky)
                    .create();
            Class<?> paramType = method.getParameterTypes()[0];
            appendData(paramType, busMethod);

            if (sticky) dealSticky(busMethod);
        }
    }
    
    private List<Method> findUsefulMethods(@NonNull Object observer) {
        List<Method> usefulMethods = new ArrayList<>();
        Class<?> observerClass = observer.getClass();
        Method[] methods = observerClass.getDeclaredMethods();
        for (Method method : methods) {
            if (checkIsUsefulMethod(method)) {
                usefulMethods.add(method);
            }
        }
        return usefulMethods;
    }
    
    private boolean checkIsUsefulMethod(@NonNull Method method) throws EventException {
        Subscribe subAnnotation = method.getAnnotation(Subscribe.class);
        if (null != subAnnotation) {
            Class<?> returnType = method.getReturnType();
            if (!returnType.equals(Void.TYPE)) {
                throw new EventException("注册方法的返回值必须为void");
            }

            Class<?>[] paramsTypes = method.getParameterTypes();
            if (null == paramsTypes || paramsTypes.length != 1) {
                throw new EventException("注册方法的参数数量必须为1个");
            }
            return true;
        }
        return false;
    }
    
    private EventThread findMethodThread(@NonNull Method method) {
        Subscribe subAnnotation = method.getAnnotation(Subscribe.class);
        if (null != subAnnotation) {
            return subAnnotation.eventThead();
        }
        return EventThread.DEFAULT;
    }

    private boolean isStickEvent(@NonNull Method method) {
        Subscribe subAnnotation = method.getAnnotation(Subscribe.class);
        if (null != subAnnotation) {
            return subAnnotation.sticky();
        }
        return false;
    }

    private int getPriority(@NonNull Method method) {
        Subscribe subAnnotation = method.getAnnotation(Subscribe.class);
        if (null != subAnnotation) {
            return subAnnotation.priority();
        }
        return 0;
    }
    
    private void appendData(Class<?> paramType, BusMethod busMethod) {
        synchronized (BusHolder.data) {
            TreeSet<BusMethod> busMethods = BusHolder.data.get(paramType);
            if (null == busMethods) {
                busMethods = new TreeSet<>();
            }
            busMethods.add(busMethod);
            BusHolder.data.put(paramType, busMethods);
        }
    }
    
    public void dealSticky(BusMethod busMethod) {
        synchronized (BusHolder.sticks) {
            Iterator<Object> iterator = BusHolder.sticks.iterator();
            Class<?> paramType = busMethod.getMethod().getParameterTypes()[0];
            while (iterator.hasNext()) {
                Object event = iterator.next();
                if (event.getClass().equals(paramType)) {
                    executeMethodWithThread(busMethod, event);
                }
            }
        }
    }

虽然代码看起来很多,其实就只是新增了一个处理粘性事件的逻辑。主要就是把很多逻辑有方法名的处理换成了对注解解析的处理。这里需要特别注意的是,我们把所有涉及到对BusHolder.sticks和BusHolder.data遍历或者其他改动的方法均加上锁,防止多线程处理的时候出错。
然后是post和postSticks方法。

public void post(@NonNull Object event) {
        Class<?> eventClass = event.getClass();
        TreeSet<BusMethod> busMethods = BusHolder.data.get(eventClass);
        if (null != busMethods) {
            Iterator<BusMethod> iterator = busMethods.iterator();
            while (iterator.hasNext()) {
                BusMethod busMethod = iterator.next();
                executeMethodWithThread(busMethod, event);
            }
        }
    }
    
public void postSticky(@NonNull Object event) {
        this.post(event);
        BusHolder.sticks.add(event);
    }
    
private void executeMethodWithThread(BusMethod busMethod, Object event) {
        switch (busMethod.getEventThread()) {
            case DEFAULT:
                executeMethod(event, busMethod);
                break;
            case MAIN:
                new MainHandler(event, busMethod).sendEmptyMessage(0);
                break;
            case ASYNC:
                executeMethodAsync(event, busMethod);
                break;
            case BACKGROUND:
                if (Looper.myLooper() == Looper.getMainLooper()) {
                    executeMethodAsync(event, busMethod);
                } else {
                    executeMethod(event, busMethod);
                }
                break;
            default:
                executeMethod(event, busMethod);
                break;
        }
    }
    
private void executeMethodAsync(final Object event, final BusMethod busMethod) {
        new Thread() {
            @Override
            public void run() {
                executeMethod(event, busMethod);
            }
        }.start();
    }

private void executeMethod(Object event, BusMethod busMethod) {
        Object observer = busMethod.getObserver();
        //确定观察者尚未被回收
        if (null != observer) {
            Method method = busMethod.getMethod();
            try {
                if (!method.isAccessible()) method.setAccessible(true);
                method.invoke(observer, event);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

除了对粘性事件的处理之外和之前的逻辑一样。
unregister的处理和之前的处理完全一致,这里我就不贴代码了。

着陆

测试代码大家就自己试试吧,另外这个项目我也上传了我的github,大家可以自己去下载来试试。MiniBus

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

推荐阅读更多精彩内容