转载文章请标明出处 http://www.jianshu.com/p/476a3a6dc789
如果你没有看过我之前的eventbus系列的文章建议先去看看
上一篇文章我们用了第三种方案实现了我们的EventBus,并且完善了线程切换功能。用过最新版的EventBus的朋友都知道,我们的EventBus还远远没有人家的好用,原因主要是
- 不支持注解方式配置观察者方法,定义出来的方法方法名太长很难看
- 不支持事件优先级配置
- 不支持粘性事件
今天这次呢,我们就来解决这些问题。
梦想起航
首先我们在原有工程下新建一个包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里面新增了个粘性事件的集合来存储粘性事件。其实粘性事件的处理很简单。
- 在执行postSticky方法后将event存储到sticks中去
- 在有新的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