教你快速实现一个EventBus框架

前言

EventBus相信大多数人都用过,其具有方便灵活、解耦性强、体积小、简单易用等优点,虽然现在也有很多优秀的替代方案如RxBus、LiveDataBus等,但不可否认EventBus开创了消息总线时代,有很多优秀的思想可以供我们来借鉴学习。下面就让我们来撸一个超简单的EventBus,领略其实现方式的主要思想。

实现思路

先看一下GitHub中官方用法介绍:
1.定义消息实体类

public static class MessageEvent { /* Additional fields if needed */ }

2.准备订阅者:声明并注解订阅方法

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

注册和注销订阅者

@Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

3.发送事件

EventBus.getDefault().post(new MessageEvent());

EventBus通过register方法,查找注册对象中有Subscribe注解的方法,并把这些方法维护在EventBus自己的Map中,在某处调用post()方法发送消息时,通过消息载体Bean类,在Map中查找订阅了接收该Bean类的消息接收方法,调用该方法,达到消息传递的目的。

功能实现

  • 准备工作

1.定义线程枚举类

public enum ThreadMode {
    MAIN,
    BACKGROUND
}

2.定义注解,注解目标是函数,运行时注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.MAIN;
}

3.定义方法封装类,用来描述使用了subscribe注解的消息接收方法,将维护在EventBus类中。

public class SubscribeMethod {

    /**
     * 方法本身
     */
    private Method method;

    /**
     * 线程模式
     */
    private ThreadMode threadMode;

    /**
     * 参数类型
     */
    private Class<?> type;


    public SubscribeMethod(Method method, ThreadMode threadMode, Class<?> type) {
        this.method = method;
        this.threadMode = threadMode;
        this.type = type;
    }


    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<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }
}
  • 定义EventBus类
    单例模式,定义一个Map维护订阅的方法
    private static volatile EventBus instance;
    private Map<Object, List<SubscribeMethod>> cacheMap;

    private EventBus() {
        cacheMap = new HashMap<>();
    }

    public static EventBus getDefault() {
        if (instance == null) {
            synchronized (EventBus.class) {
                if (instance == null) {
                    instance = new EventBus();
                }
            }
        }
        return instance;
    }

定义注册方法,在该方法中,查找订阅的方法,维护在map中,该map的key是注册消息的对象,如Activity、Fragment等

    public void register(Object obj) {
        //找到消息监听方法,如onMessageEvent,获取信息,统一管理
        List<SubscribeMethod> list = cacheMap.get(obj);
        if (list == null) {
            list = findSubscribeMethods(obj);
            cacheMap.put(obj, list);
        }
    }

其中findSubscribeMethods(obj)方法是用来查找对象中订阅方法,下面是其实现

    /**
     * 查找定义的消息接收方法
     *
     * @param obj 查找对象
     * @return
     */
    private List<SubscribeMethod> findSubscribeMethods(Object obj) {
        List<SubscribeMethod> list = new ArrayList<>();
        Class<?> clazz = obj.getClass();
        //寻找自身及父类subscribe注解的方法
        while (clazz != null) {
            //如果是系统的类,过滤掉
            String name = clazz.getName();
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                break;
            }
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method method : declaredMethods) {
                //找到带有subscribe注解的方法
                Subscribe subscribe = method.getAnnotation(Subscribe.class);
                if (subscribe == null) {
                    continue;
                }
                Class<?>[] parameterTypes = method.getParameterTypes();
                //这里仅考虑只有一个参数的情况
                if (parameterTypes.length != 1) {
                    throw new IllegalArgumentException("消息监听方法有且只能有一个参数!");
                }
                ThreadMode threadMode = subscribe.threadMode();
                SubscribeMethod subscribeMethod = new SubscribeMethod(method, threadMode, parameterTypes[0]);
                list.add(subscribeMethod);
            }
            clazz = clazz.getSuperclass();
        }
        return list;
    }

定义消息发送方法

    /**
     * 发送消息
     *
     * @param type 消息实体
     */
    public void post(Object type) {
        //循环cacheMap,找到对应的方法进行回调
        Set<Object> set = cacheMap.keySet();
        Iterator<Object> iterator = set.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            List<SubscribeMethod> list = cacheMap.get(obj);
            for (SubscribeMethod subscribeMethod : list) {
                //匹配发送的消息bean和接收的消息bean是否相同
                if (subscribeMethod.getType().isAssignableFrom(type.getClass())) {
                    invoke(subscribeMethod, obj, type);
                }
            }
        }
    }

其中invoke(subscribeMethod, obj, type)反射调用回调的方法,实现如下

    /**
     * 反射调用回调方法
     *
     * @param subscribeMethod 方法对象
     * @param obj             被反射对象
     * @param type            方法参数
     */
    private void invoke(SubscribeMethod subscribeMethod, Object obj, Object type) {
        Method method = subscribeMethod.getMethod();
        try {
            method.invoke(obj, type);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

OK!这么一个最简单的EventBus就写完了,现在让我们来测试一下吧

测试效果

我们打算在MainActivity中订阅,在SecondActivity中发送消息来测试
首先定义一个消息实体类

public class MessageBean {

    private String param1;

    private String param2;

    public MessageBean(String param1, String param2) {
        this.param1 = param1;
        this.param2 = param2;
    }

    public String getParam1() {
        return param1;
    }

    public void setParam1(String param1) {
        this.param1 = param1;
    }

    public String getParam2() {
        return param2;
    }

    public void setParam2(String param2) {
        this.param2 = param2;
    }


    @Override
    public String toString() {
        return "MessageBean{" +
                "param1='" + param1 + '\'' +
                ", param2='" + param2 + '\'' +
                '}';
    }
}

MainActivity中注册订阅

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Main";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EventBus.getDefault().register(this);

        startActivity(new Intent(this, SecondActivity.class));
    }


    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageBean event) {
        if (event != null) {
            Log.e(TAG, event.toString());
        }
    }
}

收到消息后会打印出消息内容
最后在SecondActivity中发送消息

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        findViewById(R.id.tv_post).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MessageBean("SecondActivity","消息内容"));
            }
        });
    }
}

运行后,点击SecondActivity中的发送按钮后,打印log如下:

com.cwq.eventbusdemo E/Main: MessageBean{param1='SecondActivity', param2='消息内容'}

大功告成!
当然了,跟正式的EventBus相比,我们都很多东西没有处理,比如注销、非主线程的消息处理、粘性消息、甚至新版的进程间通讯等等,这里仅仅实现最简单的功能,让大家清晰明白EventBus的工作方式,更多细节还要研习源码来学习,一个优秀的框架就是一个学习宝藏,都值得我们去借鉴和学习。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容