手撸EventBus

590 阅读6分钟

1、简介

作为一个android开发者进程间通信,开发一个产品或多或少都会遇到;能用系统自带的Intent,handler实现是最好的;如果不能,大家应该都会想到一个第三方库EventBus;这个库确实简单、好用;

虽然是手撸,还是按照EventBus核心代码思路来搞的,参考版本3.2.0;

2、基础准备

我们需要准备注解知识、线程切换知识、观察者模式等即可;

2.1 注解

注解本质是一个继承了 Annotation 的特殊接口,其具体实现类是 Java 运行时生成的动态代理类。下面就是EventBus中定义的一个注解类

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
    boolean sticky() default false;
    int priority() default 0;
}

其实就是定义一种接口;下面就先解释以下这个注释

  1. @interface表示这是一个注释
  2. @Document表示注释被包含在javadoc中
  3. @Retention表示注释保留的生命周期可选的RetentionPolicy参数包括:
    • SOURCE:注解将被编译器丢弃
    • CLASS:注解在class文件中可用,但会被VM丢弃
    • RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。
  4. @Target 该注解可以用于什么地方,可能的ElementType参数有:
    • CONSTRUCTOR:构造器的声明
    • FIELD:域声明(包括enum实例)
    • LOCAL_VARIABLE:局部变量声明
    • METHOD:方法声明
    • PACKAGE:包声明
    • PARAMETER:参数声明
    • TYPE:类、接口(包括注解类型)或enum声明

使用此注解时只需要在方法上方加入下面代码即可

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)

注解的获取,是通过反射来获取,比如上述代码需要,先获取Method对象,再调用Method.getAnnotation(Class var1)方法即可获取Subscribe对象,通过相应方法就可以获得配置信息了

其实上面解释的都是元注解,这是系统已经规定好,1-4是可以定义注解的注解,也就是自定义会用到上述注解;自定义注解还有一个 @Inherited允许子类继承父类中的注解

其它一些常见元注解

  • @Override,表示当前的方法定义将覆盖超类中的方法。

  • @Deprecated,使用了注解为它的元素编译器将发出警告,因为注解@Deprecated是不赞成使用的代码,被弃用的代码。

  • @SuppressWarnings,关闭不当编译器警告信息。

@Retention:很重要,决定了你要如何获取注解信息,如果是RUNTIME,直接通过反射获取;如果是SOURCE,则通过java注解器+反射+字节码生成

2.2 线程切换

其实线程切换并没有那么复杂,主线程切换用Handler机制来处理,子线程用线程池来处理即可

2.3 观察者模式

观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

3、开始手撸

既然是观察者模式,那么就有发布者,和接收者,而且要有一套接收者能够正确接收消息的机制;

3.1 使用注解建立接收者信息

比如下面,可以配置接收者执行线程和优先级的方法注解,方法即为接收者,方法传参类型作为接收者接收消息类型

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

既然注解已经处理好了,那么我们就定义一个包含接收者信息的类吧;那么这类应该包括

  1. 反射调用方法所包含的信息:调用者对象,方法对象
  2. 注解信息:优先级、执行线程信息
  3. 调用方法的参数信息

消息类

  final class Subscription {
    Object subscriber;
    Method method;
    Class<?> type;
    int priority;
    ThreadMode mode;

    public Subscription(Object subscriber, Method method, Class<?> type, int priority, ThreadMode mode) {
        this.subscriber = subscriber;
        this.method = method;
        this.type = type;
        this.priority = priority;
        this.mode = mode;
    }

    public void doMethod(Object event) {
        try {
            method.invoke(subscriber, event);
        } catch (Throwable e) {}
    }
}

3.2 建立发送者-接收者信息传递机制

需要建立消息类型到消息接收者的集合映射,使用全局模式;比如下面的接收者信息集合:以方法参数的class为索引,以方法等信息为value来存储

  private HashMap<Class<?>, CopyOnWriteArrayList<Subscription>> maps;

注册消息:

   public void register(Object obj) {
      if (obj != null) {
          Method[] methods = obj.getClass().getDeclaredMethods();
          if (methods != null && methods.length > 0) {
              if (maps == null) {
                  maps = new HashMap<>();
              }
              Subscription subscription;
              for (Method method: methods) {
                  Class<?>[] params = method.getParameterTypes();
                  if (params == null || params.length != 1) {
                      continue;
                  }
                  Subscribe subscribe = method.getAnnotation(Subscribe.class);
                  if (subscribe == null) {
                      continue;
                  }
                  subscription = new Subscription(obj, method, params[0], subscribe.priority(), subscribe.threadMode());

                  CopyOnWriteArrayList<Subscription> subscriptions = maps.get(params[0]);
                  if (subscriptions == null) {
                      subscriptions = new CopyOnWriteArrayList<>();
                      maps.put(params[0], subscriptions);
                  }
                  if (subscriptions.isEmpty()) {
                      subscriptions.add(subscription);
                  } else {
                      int index = 0;
                      for (; index < subscriptions.size(); index++) {
                          if (subscription.priority < subscriptions.get(index).priority) {
                              break;
                          }
                      }
                      subscriptions.add(index, subscription);
                  }
              }
          }
      }
  }

发送消息:

   public void post(final Object event) {
        if (event != null && maps != null && !maps.isEmpty()) {
            CopyOnWriteArrayList<Subscription> subscriptions = maps.get(event.getClass());
            if (subscriptions == null) {
                return;
            }

            for (final Subscription subscription : subscriptions) {
                switch (subscription.mode) {
                    case POSTING:
                        subscription.doMethod(event);
                        break;
                    case MAIN:
                        Handler handler = new Handler(Looper.getMainLooper());
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                subscription.doMethod(event);
                            }
                        });
                        break;
                    default:
                        service.submit(new Runnable() {
                            @Override
                            public void run() {
                                subscription.doMethod(event);
                            }
                        });
                }
            }
        }
    }

接收者:

    @Subscribe(priority = 1, threadMode = ThreadMode.MAIN)
    public void onMessage(String str) {
        view.setText(str);
    }

发送消息:

EventBus.get().post("nihao event bus!");

解注:

    public void unRegister(Object obj) {
        if (obj != null) {
            Method[] methods = obj.getClass().getDeclaredMethods();
            if (methods != null && methods.length > 0) {
                if (maps == null) {
                    maps = new HashMap<>();
                    return;
                }

                for (Method method: methods) {
                    if (method.getAnnotation(Subscribe.class) == null) {
                        continue;
                    }
                    Class<?>[] params = method.getParameterTypes();
                    if (params == null || params.length != 1) {
                        continue;
                    }

                    CopyOnWriteArrayList<Subscription> subscriptions = maps.get(params[0]);
                    if (subscriptions == null) {
                        return;
                    }

                    int index = 0;
                    for (; index < subscriptions.size(); index++) {
                        if (subscriptions.get(index).subscriber == obj) {
                            break;
                        }
                    }
                    subscriptions.remove(index);
                }
            }
        }
    }

通过上面两步骤,已经完成最简单暴力的版本;其中仍然可以增加class-method的映射缓存等,还可以做一些粘性消息等新增功能

4、 EventBus中的线程切换处理

首先定义了一个Poster接口;仅有一个方法

interface Poster {
  void enqueue(接收者信息 Subscription subscription, 消息 Object event);
}

实现中均使用了单列表的先进先出的队列PendingPostQueue,节点PendingPost封装了接收者、消息信息,并使用了池技术

定义了三种实现:

  1. 异步处理 AsyncPoster:实现了Runnable接口,通过enqueue方法加入接收者-消息,调用线程池执行当前对象
  2. 后台处理 BackgroundPoster:实现了Runnable接口,通过enqueue方法加入接收者-消息,如果当前run方法未执行,则调用线程池执行; run方法采用死循环方式,尽可能尝试从队列中读取,1s后仍然未有处理,则run方法执行结束
  3. 主线程处理HandlerPoster,继承Handler,通过enqueue方法加入接收者-消息,如果当前handleMessage方法不再运行状态,则发送消息,通知执行;执行时,也使用了死循环,跳出的条件,执行时长小于某个值,默认10ms

为什么处理的过程中基本都使用了死循环呢,而又给处理跳转条件呢?

  1. 死循环是为了,更及时的把消息处理掉
  2. 结束条件,是为了不阻塞主线程或者,节约线程资源

5、小结

自己手撸EventBus,根据所需功能来定,上面小结中手撸的代码60%的都够用了,而且很简单;那么我们在具体阻止代码时,应该注意什么呢?我认为至少要注意以下3点:

  1. 注意线程安全
  2. 注意不要内存泄漏,注册和解注要成对实现(也可利用弱引用+加弱引用队列来处理)
  3. 建立一套触发机制,保证消息得到处理

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!