EventBus原理解析,纯手工带你撸代码实现EventBus框架

github源码分享:https://github.com/greenrobot/EventBus

官方图解:

EventBus是Android和Java的发布/订阅事件总线。

EventBus优势:

1、简化了组件之间的通信:

将事件发送者和接收者分离

在活动,片段和后台线程中表现良好

避免复杂且容易出错的依赖关系和生命周期问题

2、使您的代码更简洁

3、很快

4、很小(约50kb)

5、已经通过100,000,000+安装的应用程序在实践中得到证实

6、具有交付线程,用户优先级等高级功能;

总结:

EventBus的使用比较简单,可以按照官网图的方式来进行理解。EventBus相当于邮箱,发布者直接把信息放到信箱,信箱会自动把信息发给订阅了的人。

这里简单梳理一下EventBus的主要逻辑:

1、EventBus对象的构建,使用单列模式创建

2、注册事件,包含粘性事件

3、发布消息

4、接受消息

5、反注册事件,避免内存泄漏

(一)、EventBus构造方法原理


这里就简单的使用单例模式实现

EventBus.java

private static EventBus instance;

public static EventBus getDefault() {

    if (instance == null) {

        synchronized (EventBus.class) {

            if (instance == null) {

                instance = new EventBus();

            }

        }

    }

    return instance;

}

(二)、注册事件


注册就是将此类中的所有的带Subscriber注解的方法缓存起来,以便发布消息时调用;

我们也是简单的模拟实现注册,不包含粘性事件的注册

需求:从AActivity跳转到BActivity中,在BActivity中发布消息给AActivity;

//比如在AActivity接受事件,我们就在AActivity中注册一个接收消息的方法

如:

MainActivity.java

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

  //注册

    EventBus.getDefault().register(this);

}

@Subscribe(threadMode = ThreadMode.BACKGROUND)

public void handleMessages(EventBean eventBean) {

    Log.d("======>", eventBean.toString());

    Log.d("======", "send: MainActivity " + Thread.currentThread().getName());

}

Subscribe是一个注解类,用来标记一个方法

Subscribe.java

@Documented

@Retention(RetentionPolicy.RUNTIME)// 表示为运行时注解

@Target({ElementType.METHOD})

public @interface Subscribe {

    // 指定事件订阅方法的线程模式,即在那个线程执行事件订阅方法处理事件,默认为POSTING

    ThreadMode threadMode() default ThreadMode.POSTING;

    // 是否支持粘性事件,默认为false

    boolean sticky() default false;

    // 指定事件订阅方法的优先级,默认为0,如果多个事件订阅方法可以接收相同事件的,则优先级高的先接收到事件

    int priority() default 0;

}

ThreadMode是一个枚举类,用来标记此方法在那个线程执行

ThreadMode.java

public enum ThreadMode {

    POSTING,//默认的线程模式,在那个线程发送事件就在对应线程处理事件,避免了线程切换,效率高。

    MAIN,//如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。

    MAIN_ORDERED,//无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。

    BACKGROUND,//如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件;如果在子线程发送事件,则直接在发送事件的线程处理事件。

    ASYNC//无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。

}

private Map<Object, List<SubscriberMethod>> cacheMap;// 用来缓存注册\订阅的事件

public void register(Object subscriber) {

    // 检查此类是否已经注册

    List<SubscriberMethod> subscriberMethods = cacheMap.get(subscriber);

    if (subscriberMethods == null) {

        subscriberMethods = findSubscriberMethods(subscriber);

        cacheMap.put(subscriber, subscriberMethods);

    }

}

/**

* 查找此类以及父类中所有带Subscriber注释的方法

* @param subscriber

* @return

*/

private List<SubscriberMethod> findSubscriberMethods(Object subscriber) {

    List<SubscriberMethod> list = new ArrayList<>();

    Class<?> clazz = subscriber.getClass();

    while (clazz != null) {

        String name = clazz.getName();

        // 过滤系统类

        if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {

            break;

        }

        Method[] methods = clazz.getDeclaredMethods();// 获取此类中所有的的方法(包含继承父类中的)

        for (Method method : methods) {

            Subscribe annotation = method.getAnnotation(Subscribe.class);// 找到方法上的注解参数

            if (annotation == null) {

                continue;

            }

            //获取所有带有Subscribe注解的方法中的参数类型

            Class<?>[] parameterTypes = method.getParameterTypes();

            if (parameterTypes.length != 1) {// 只记录带有一个参数的方法

                Log.e("error", "EventBus only support one para");

                continue;

            }

            ThreadMode threadMode = annotation.threadMode();

            SubscriberMethod subscriberMethod = new SubscriberMethod(method, parameterTypes[0], threadMode, 0, false);

            list.add(subscriberMethod);

        }

        clazz = clazz.getSuperclass();

    }

    return list;

}

SubscriberMethod.java 带有注解的方法对象

public class SubscriberMethod {

    private Method method;

    private ThreadMode threadMode;

    private Class<?> eventType;

    private int priority;

    private boolean sticky;

    /**

    * Used for efficient comparison

    */

    String methodString;

    public SubscriberMethod(Method method, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky) {

        this.method = method;

        this.threadMode = threadMode;

        this.eventType = eventType;

        this.priority = priority;

        this.sticky = sticky;

    }

    @Override

    public boolean equals(Object other) {

        if (other == this) {

            return true;

        } else if (other instanceof SubscriberMethod) {

            checkMethodString();

            SubscriberMethod otherSubscriberMethod = (SubscriberMethod) other;

            otherSubscriberMethod.checkMethodString();

            // Don't use method.equals because of http://code.google.com/p/android/issues/detail?id=7811#c6

            return methodString.equals(otherSubscriberMethod.methodString);

        } else {

            return false;

        }

    }

    private synchronized void checkMethodString() {

        if (methodString == null) {

            // Method.toString has more overhead, just take relevant parts of the method

            StringBuilder builder = new StringBuilder(64);

            builder.append(method.getDeclaringClass().getName());

            builder.append('#').append(method.getName());

            builder.append('(').append(eventType.getName());

            methodString = builder.toString();

        }

    }

    @Override

    public int hashCode() {

        return method.hashCode();

    }

    public Method getMethod() {

        return method;

    }

    public ThreadMode getThreadMode() {

        return threadMode;

    }

    public Class<?> getEventType() {

        return eventType;

    }

    public int getPriority() {

        return priority;

    }

    public boolean isSticky() {

        return sticky;

    }

    public String getMethodString() {

        return methodString;

    }

}

以上就是事件注册\订阅的完整流程

(三)、发布消息


SecondActivity.java中任意位置

EventBus.getDefault().post(new EventBean("EventBusMassage", 111111111));

EventBus.java

public void post(final Object obj) {

    // 从注册事件中循环查询

    Set<Object> objects = cacheMap.keySet();

    Iterator<Object> iterator = objects.iterator();

    while (iterator.hasNext()) {

        final Object next = iterator.next();

        List<SubscriberMethod> list = cacheMap.get(next);

        for (final SubscriberMethod subsMethod : list) {

            // 获取到的类的类型是否和传入或者发送消息的类对象类型相同

            if (subsMethod.getEventType().isAssignableFrom(obj.getClass())) {

                switch (subsMethod.getThreadMode()) {

                    case MAIN:

                        // 主线程---》主线程

                        if (Looper.myLooper() == Looper.getMainLooper()) {

                            invoke(subsMethod, next, obj);

                        } else { // 子线程---》主线程

                            mHandler.post(new Runnable() {

                                @Override

                                public void run() {

                                    invoke(subsMethod, next, obj);

                                }

                            });

                        }

                        break;

                    case BACKGROUND:

                        // 主线程---》子线程

                        if (Looper.myLooper() == Looper.getMainLooper()) {

                            ExecutorService executorService = Executors.newFixedThreadPool(10);

                            executorService.execute(new Runnable() {

                                @Override

                                public void run() {

                                    invoke(subsMethod, next, obj);

                                }

                            });

                        } else { // 子线程---》子线程

                            invoke(subsMethod, next, obj);

                        }

                        break;

                }

            }

        }

    }

}

// 方法调用并执行

private void invoke(SubscriberMethod subsMethod, Object next, Object obj) {

    Method method = subsMethod.getMethod();

    try {

        method.invoke(next, obj);

    } catch (IllegalAccessException e) {

        e.printStackTrace();

    } catch (InvocationTargetException e) {

        e.printStackTrace();

    }

}

(四)、接受消息

MainActivity.java

@Subscribe(threadMode = ThreadMode.BACKGROUND)

public void handleMessages(EventBean eventBean) {

    Log.d("======>", eventBean.toString());

    Log.d("======", "handleMessages: MainActivity " + Thread.currentThread().getName());

}

(五)、注销事件

MainActivity.java

@Override

protected void onDestroy() {

    super.onDestroy();

    EventBus.getDefault().unregister(this);

}

EventBus.java

// 注销事件

public void unregister(Object subscriber) {

    List<SubscriberMethod> subscribedTypes = cacheMap.get(subscriber);

    if (subscribedTypes != null) {

        cacheMap.remove(subscriber);

    } else {

        Log.e("error", "Subscriber to unregister was not registered before: " + subscriber.getClass());

    }

}

参考文档://www.greatytc.com/p/d9516884dbd4

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

推荐阅读更多精彩内容

  • 项目到了一定阶段会出现一种甜蜜的负担:业务的不断发展与人员的流动性越来越大,代码维护与测试回归流程越来越繁琐。这个...
    fdacc6a1e764阅读 3,163评论 0 6
  • EventBus基本使用 EventBus基于观察者模式的Android事件分发总线。 从这个图可以看出,Even...
    顾氏名清明阅读 616评论 0 1
  • 先吐槽一下博客园的MarkDown编辑器,推出的时候还很高兴博客园支持MarkDown了,试用了下发现支持不完善就...
    Ten_Minutes阅读 557评论 0 2
  • EventBus用法及解析 EventBus介绍: EventBus主要是用来组件之间进行信息传递的,相对于接口回...
    111_222阅读 542评论 0 1
  • EventBus 简介 EventBus 直译过来就是事件总线,熟悉计算机原理的人一定很熟悉总线的概念,所有设备都...
    DanieX阅读 1,035评论 0 1