Android知名三方库EventBus手写实现

源代码
GitHub源代码

本文目标

实现简易版的EventBus

基本用法

现在有两个页面,MainActivity 和 TestActivity,在MainActivity中注册EventBus事件,然后在TestActivity中发射事件

public class MainActivity extends AppCompatActivity {

    private TextView mTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 注册
        EventBus.getDefault().register(this);

        // 进入测试界面
        mTv = (TextView) findViewById(R.id.test_tv);
        mTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,TestActivity.class);
                startActivity(intent);
            }
        });
    }

    /**
     * threadMode 执行的线程方式
     * priority 执行的优先级
     * sticky 粘性事件
     */
    @Subscribe(threadMode = ThreadMode.MAIN,priority = 50,sticky = true)
    public void test1(String msg){
        // 如果有一个地方用 EventBus 发送一个 String 对象,那么这个方法就会被执行
        Log.i("TAG","msg1 = "+msg);
        mTv.setText(msg);
    }

    /**
     * threadMode 执行的线程方式
     * priority 执行的优先级,值越大优先级越高
     * sticky 粘性事件
     */
    @Subscribe(threadMode = ThreadMode.MAIN,priority = 100,sticky = true)
    public void test2(String msg){
        // 如果有一个地方用 EventBus 发送一个 String 对象,那么这个方法就会被执行
        Log.i("TAG","msg2 = "+msg);
        mTv.setText(msg);
    }

    @Override
    protected void onDestroy() {
        // 解绑
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }
}

在TestActivity中去发射事件

public class TestActivity extends AppCompatActivity {

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

        findViewById(R.id.test_tv).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post("今天天气真晴朗");
                finish();
            }
        });
    }
}

手写之前明确几个基本概念

在手写之前一定要先弄明白EventBus中的两个字段(两个map)

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
上面这个字段是个map集合
|-- key: Event函数的参数的类(本例中是String.class)
|-- value: Subscription 的集合列表
|-- Subscription包含两个属性
|-- 一个是subscriber 订阅者(反射执行对象,本例中是MainActivity.class)
|-- 一个是SubscriberMethod 注解方法的所有属性参数值(threadModel线程模型 priority优先级 sticky是否黏性 eventType事件类型class method方法对象)

private final Map<Object, List<Class<?>>> typesBySubscriber;
上面这个字段是个map集合,主要用于解绑使用
|-- key: 所有的订阅者(本例中是MainActivity.class)
|-- value: 所有订阅者里面方法的参数的class() (本例中是 String.class 的集合)
目的是在unregister()解绑中通过key(MainActivity.class)找到value(String.class),
然后在通过value(String.class)找到subscriptionsByEventType中的value()Subscription 的集合列表,
然后就可以进行移除操作了

上述两个map是在EventBus的构造方法中去实现的

public class EventBus {

    // key 是 Event 参数的类(本例中是String.class)
    // value 存放的是 Subscription 的集合列表
    // Subscription 包含两个属性,一个是 subscriber 订阅者(反射执行对象,本例中是MainActivity.class),一个是 SubscriberMethod 注解方法的所有属性参数值
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

    // key 是所有的订阅者(MainActivity.class)
    // value 是所有订阅者里面方法的参数的class() (本例中就是 String.class 的集合)
    private final Map<Object, List<Class<?>>> typesBySubscriber;

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

    private volatile static EventBus INSTANCE = null;

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

1.EventBus.getDefault().register()

    public void register(Object object) {
        //第一步
        ArrayList<SubscriberMethod> subscriberMethods = new ArrayList<>();
        //1.先拿到object的字节码class对象
        Class<?> objectClass = object.getClass();
        Method[] declaredMethods = objectClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            //2.获取到该class对象的所有标记@Subscribe注解的函数
            Subscribe annotation = method.getAnnotation(Subscribe.class);
            if(annotation!=null){
                // 所有的Subscribe属性 解析出来
                Class<?>[] parameterTypes = method.getParameterTypes();
                //3.解析被注解标记的函数成SubscriberMethod对象(threadModel线程模型 priority优先级 sticky是否黏性 eventType事件类型class method方法对象)
                SubscriberMethod model = new SubscriberMethod(method, parameterTypes[0], annotation.threadMode(), annotation.priority(), annotation.sticky());
                //4.然后存到subscriberMethods集合中
                subscriberMethods.add(model);
            }
        }
        //第二步
        //1.遍历subscriberMethods的List集合
        for (SubscriberMethod method : subscriberMethods) {
            subscribe(object,method);
        }
    }

    private void subscribe(Object object, SubscriberMethod method) {
        //2.按照规则存放到subscriptionsByEventType的map集合中(key:String.class  value:CopyOnWriteArrayList<Subscription>)
        Class<?> eventType = method.eventType;
        //先从subscriptionsByEventType 的map中取一下List,如果为空则创建一个,然后在添加
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if(subscriptions==null){
            subscriptions=new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType,subscriptions);
        }
        Subscription subscription = new Subscription(object, method);
        subscriptions.add(subscription);

        //第三步:
        //1.typesBySubscriber的map集合也要存一份(key:MainActivity.class value:List<Class<?> String.class 的集合)
        List<Class<?>> eventTypeList = typesBySubscriber.get(object);
        if(eventTypeList==null){
            eventTypeList=new ArrayList<>();
            typesBySubscriber.put(object,eventTypeList);
        }
        if(!eventTypeList.contains(eventType)){
            eventTypeList.add(eventType);
        }
    }

register的核心思路
第一步:

  • 1.先拿到object的字节码class对象
  • 2.获取到该class对象的所有标记@Subscribe注解的函数
  • 3.解析被注解标记的函数成SubscriberMethod对象(threadModel线程模型 priority优先级 sticky是否黏性 eventType事件类型class method方法对象)
  • 4.然后存到subscriberMethods集合中
    第二步:
  • 1.遍历subscriberMethods的List集合
  • 2.按照规则存放到subscriptionsByEventType的map集合中(key:String.class value:CopyOnWriteArrayList<Subscription>)
    第三步:
  • 1.typesBySubscriber的map集合也要存一份(key:MainActivity.class value:List<Class<?> String.class 的集合)

2.EventBus.getDefault().unregister()

    public void unregister(Object object) {
        //1.通过typesBySubscriber这个map得到所有的String.class集合
        List<Class<?>> eventTypList = typesBySubscriber.get(object);
        if(eventTypList!=null){
            //2.遍历这个String.class集合
            for (Class<?> eventType : eventTypList) {
                removeObject(eventType,object);
            }
        }
    }

    private void removeObject(Class<?> eventType, Object object) {
        //3.通过subscriptionsByEventType这个map获取到CopyOnWriteArrayList<Subscription>
        CopyOnWriteArrayList<Subscription> subscriptionList = subscriptionsByEventType.get(eventType);
        if(subscriptionList!=null){
            int size = subscriptionList.size();
            //4.遍历CopyOnWriteArrayList集合,然后挨个移除
            for(int x=0;x<size;x++){
                //5.得到Subscription,然后subscription.subscriber==object 进行比较,如果相同则移除
                Subscription subscription = subscriptionList.get(x);
                if(subscription.subscriber==object){
                    subscriptionList.remove(x);
                    x--;
                    size--;
                }
            }
        }
    }

unregister的核心思路

  • 1.通过typesBySubscriber这个map得到所有的String.class集合
  • 2.遍历这个String.class集合
  • 3.通过subscriptionsByEventType这个map获取到CopyOnWriteArrayList<Subscription>
  • 4.遍历CopyOnWriteArrayList集合
  • 5.得到Subscription,然后subscription.subscriber==object 进行比较,如果相同则移除

3.EventBus.getDefault().post()

public void post(Object event) {
        //1.先获取event的字节码对象,这其实就是eventType
        Class<?> evenType = event.getClass();
        //2.从subscriptionsByEventType中根据eventType得到Subscription 的集合列表
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(evenType);
        if(subscriptions!=null){
            //3.遍历这个集合
            for (Subscription subscription : subscriptions) {
                executeMethod(subscription,event);
            }
        }
    }

    //4.找到符合的方法调用方法的 method.invoke() 执行,要注意线程切换
    private void executeMethod(final Subscription subscription, final Object event) {
        SubscriberMethod subscriberMethod = subscription.subscriberMethod;

        //是否在主线程
        boolean isMainThread = Looper.getMainLooper() == Looper.myLooper();
        switch (subscriberMethod.threadMode){
            //同一个线程,在哪个线程发送事件,那么该方法就在哪个线程执行
            case POSTING:
                invokeMethod(subscription,event);
                break;

                // 在主线程中执行
            case MAIN:
                if(isMainThread){
                    invokeMethod(subscription,event);
                }else{
                    Handler handler = new Handler(Looper.getMainLooper());
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            invokeMethod(subscription,event);
                        }
                    });
                }
                break;

                //子线程:如果发布事件的线程是主线程,那么调用线程池中的子线程来执行订阅方法;否则直接执行;
            case BACKGROUND:
                if(isMainThread){
                    AsyncPoster.enqueue(subscription,event);
                }else{
                    invokeMethod(subscription,event);
                }
                break;

                //异步线程:无论发布事件执行在主线程还是子线程,都利用一个异步线程来执行订阅方法。
            case ASYNC:
                AsyncPoster.enqueue(subscription,event);
                break;

            default:
                break;
        }

    }

    /**
     * 真正的反射调用方法
     * @param subscription
     * @param event
     */
    private void invokeMethod(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber,event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

post的核心思路

  • 1.先获取event的字节码对象,这其实就是eventType
  • 2.从subscriptionsByEventType中根据eventType得到Subscription 的集合列表
  • 3.遍历这个集合
  • 4.找到符合的方法调用方法的 method.invoke() 执行,要注意线程切换

下面的代码是一些辅助代码,核心的代码都在上面

线程池的使用

public class AsyncPoster implements Runnable {
    final Subscription subscription;
    final Object event;

    private final static ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();

    public AsyncPoster(Subscription subscription, Object event) {
        this.subscription = subscription;
        this.event = event;
    }

    public static void enqueue(final Subscription subscription, final Object event) {
        AsyncPoster asyncPoster = new AsyncPoster(subscription, event);
        // 用线程池
        EXECUTOR_SERVICE.execute(asyncPoster);
    }

    @Override
    public void run() {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

@Subscribe注解

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

    /**
     * 是否是黏性事件
     * @return
     */
    boolean sticky() default false;

    /**
     * 优先级,值越大优先级越高
     * @return
     */
    int priority() default 0;
}

线程模型

public enum ThreadMode {

    /**
     * 同一个线程,在哪个线程发送事件,那么该方法就在哪个线程执行
     */
    POSTING,

    /**
     * 在主线程中执行
     */
    MAIN,

    /**
     * 子线程:如果发布事件的线程是主线程,那么调用线程池中的子线程来执行订阅方法;否则直接执行;
     */
    BACKGROUND,

    /**
     * 异步线程:无论发布事件执行在主线程还是子线程,都利用一个异步线程来执行订阅方法。
     */
    ASYNC
}

Subscription 包装类

final class Subscription {
    final Object subscriber; //反射执行对象,本例中是MainActivity.class
    final SubscriberMethod subscriberMethod;//方法上解析的所有信息包装类
    volatile boolean active;

    Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
        this.subscriber = subscriber;
        this.subscriberMethod = subscriberMethod;
        active = true;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof Subscription) {
            Subscription otherSubscription = (Subscription) other;
            return subscriber == otherSubscription.subscriber
                    && subscriberMethod.equals(otherSubscription.subscriberMethod);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return subscriber.hashCode() + subscriberMethod.methodString.hashCode();
    }
}

Subscription包含两个属性
|-- 一个是subscriber 订阅者(反射执行对象,本例中是MainActivity.class)
|-- 一个是SubscriberMethod 注解方法的所有属性参数值(threadModel线程模型 priority优先级 sticky是否黏性 eventType事件类型class method方法对象)

SubscriberMethod 包装类

public class SubscriberMethod {
    final Method method;//method方法对象
    final ThreadMode threadMode;//线程模型
    final Class<?> eventType;//事件类型class
    final int priority;//优先级
    final 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();
    }
}

注解方法的所有属性参数值的封装类(threadModel线程模型 priority优先级 sticky是否黏性 eventType事件类型class method方法对象)

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

推荐阅读更多精彩内容