阅读 686

对EventBus的一些思考🤔

为什么APP需要全局事件总线

现在我们总结下App在跨页面传递事件的几种方式,通过Broadcast,handler,但是通过这种方式跨组件进行通信会造成代码耦合严重,不能很好地分离发送者和接收者,EventBus进行跨组件通信的优势有哪些,第一个好处就是解耦,简化了组件间通信的方式,可以指定接收者的工作线程

如何使用EventBus

首先需要通过Gradle引入EventBus所需要的依赖

//java方式
android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                // 根据项目实际情况,指定辅助索引类的名称和包名
                arguments = [ eventBusIndex : 'com.bhx.test.MyEventBusIndex' ]
            }
        }
    }
}
dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    // 引入注解处理器
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}


//kotlin方式
apply plugin: 'kotlin-kapt'
dependencies {
    implementation 'org.greenrobot:eventbus:3.2.0'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.2.0'
}
kapt {
    arguments {
        arg('eventBusIndex', 'com.bhx.app.MyEventBusIndex')
    }
}
复制代码

如果项目混淆需要添加混淆条件

-keepattributes *Annotation*
-keepclassmembers class * {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
 
# And if you use AsyncExecutor:
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}
复制代码

然后你需要注册register和取消注册unregister,记住注册和取消注册要成对出现,不然会导致内存泄露。

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   EventBus.getDefault().register(this);
}

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

复制代码

然后你需要定义接收事件的方法,这个方法必须是Public修饰符的

@Subscribe
public void onMessageEvent(Event event){
    //TODO 处理事件
}
/**
*  运行时注解标识在方法上
**/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    /**
    *指定当前方法运行的所在线程
    */
    ThreadMode threadMode() default ThreadMode.POSTING;
    /**
    * 判断是否是粘性事件,粘性事件可以接收注册前发送过来的事件
    */
    boolean sticky() default false;

    /**
    * 指定事件的优先级,在同一threadMode下优先级高的先接收事件,如果不同threadModel的话不受影响
    */
    int priority() default 0;
}
复制代码

最后你只需要在任意地方发送事件,上面接收事件的方法就会触发,前提是event要匹配

 EventBus.getDefault().post(new Event());
复制代码

解读EventBus的源码

1.首先我们从注册开始分析

EventBus通过调用register进行页面解析获取通过@Subscribe注解的方法将方法保存到静态HashMap中,可以看下源码进行分析

public void register(Object subscriber) {
        //获取当前类对象
        Class<?> subscriberClass = subscriber.getClass();
        //调用SubscriberMethodFinder找到所有这个类以及父类通过@Subscribe注解的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        //加上同步锁用于处理多线程同步问题
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
}

复制代码

可以查找类中所有注解方法都封装在SubscriberMethodFinder这个类中,我们先来分析下这个类的源码,首先分析findSubscriberMethods这个方法,看如何处理的

 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //首先从缓存中取得订阅的方法
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        //然后通过ignoreGeneratedIndex这个参数判断是否是通过APT还是通过反射去解析当前的类,默认ignoreGeneratedIndex是false,所以默认的通过APT进行解析
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        //如果查询当前类以及父类没有@Subscriber注解的方法就会抛这个异常
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            // 将当前类的注解方法缓存起来
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }
复制代码

我们先看ignoreGeneratedIndex为false的时候的方法findUsingInfo方法

    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        //准备实例化FindState这个类,这个类主要是用于保存当前类的所有注解信息
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        //通过类的clazz进行判断一层一层的往父类去查找
        while (findState.clazz != null) {
            //获取订阅信息并保存到findState类中
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                //通过反射获取订阅信息并保存在findState
                findUsingReflectionInSingleClass(findState);
            }
            //通过View继承树往上遍历
            findState.moveToSuperclass();
        }
        //返回SubscriberMethod的集合并释放findState
        return getMethodsAndRelease(findState);
    }
    /**
    *subscriberInfoIndexes是注解处理器生成的类对象 这个类保存所有类以及类对应的SubscriberInfo
    */
    private SubscriberInfo getSubscriberInfo(FindState findState) {
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

复制代码

通过FindUsingInfo里面的方法可以看出首先是通过APT生成的类获取,然后才通过反射去获取所有的注解方法并保存到FindState中,在看下FindState这个类里面的定义

static class FindState {
        //保存当前类中所有注解的方法
        final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
        //下面三个是辅助判断是否添加过这个方法防止重复添加  
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();
        final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
        final StringBuilder methodKeyBuilder = new StringBuilder(128);
       
        Class<?> subscriberClass;
        Class<?> clazz;
        boolean skipSuperClasses;
       // 保存APT生成的订阅信息
        SubscriberInfo subscriberInfo;
       // .......省略部分代码
       
       //用于通过类的继承树获取父类
        void moveToSuperclass() {
            if (skipSuperClasses) {
                clazz = null;
            } else {
                clazz = clazz.getSuperclass();
                String clazzName = clazz.getName();
 
                if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||
                        clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
                    clazz = null;
                }
            }
        }
    }
复制代码

可以看到EventBus通过两种方案去获取当前类中所有通过@Subscribe注解的方法,首先采用通过APT注解生成类中查找所有注解的方法,一种是通过反射获取当前类所有的方法然后通过注解获取方法的签名和参数信息以及注解信息保存到SubscriberMethod中然后缓存起来。

然后我们看下取消注册又做了哪些逻辑

    /** Unregisters the given subscriber from all event classes. */
    public synchronized void unregister(Object subscriber) {
        //获取当前类中所有保存在缓存中注解的类
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

  //可以看下subscribe订阅中对这个typesBySubscriber的处理
   List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
   if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
   }
   subscribedEvents.add(eventType);

   //真正将保存的对象在集合中移出的方法
   private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }
复制代码

首先因为在register中会将当前类的所有注解的方法SubscriberMethod保存Subscription这个类中然后在保存到subscriptionsByEventType以及typesBySubscriber,可以看下这两个集合对象的定义。

//key是注解中方法参数的对象,Value是一个线程安全的ArrayList保存所有的类和方法的封装的对象
//在post的时候会通过这个集合去查找对应的Subscription进行调用
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
// 保存当前类所有的EventType然后在取消注册的时候在通过EventType去subscriptionsByEventType查找所有的Subscription并移除
private final Map<Object, List<Class<?>>> typesBySubscriber;
复制代码

2.然后我们在转回来看看Post如何发送事件的,首先我们直接看Post的方法签名

   public void post(Object event) {
       //通过ThreadLocal保存发送线程的状态
        PostingThreadState postingState = currentPostingThreadState.get();
        //将当前的事件对象添加到队列
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);
        //当前在发送的时候就不往下处理只有当前为false的时候才处理,用于多线程发送的时候进行排队发送,保证事件的有序性
        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
               //循环将队列的对象取出并发送
                while (!eventQueue.isEmpty()) {
                    //真正处理事件的方法
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }
  /**
  * event表示需要发送的事件对象,postingState保存
  */
   private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        if (eventInheritance) {
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
  final static class PostingThreadState {
        //保存事件对象
        final List<Object> eventQueue = new ArrayList<>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }
复制代码

EventBus将多线程发送事件通过ThreadLocal这个方案解决了,ThreadLocal会在每个Thread单独保存一个PostingThreadState对象这样就不会冲突了,PostingThreadState中又用List集合将所有需要发送的事件都保存起来,通过isPosting保证事件有序到达且不会丢失。

那我们看下postSingleEvent怎么处理的吧,首先lookupAllEventTypes通过这个方法将事件缓存到eventTypesCache这个集合中然后调用postSingleEventForEventType这个方法去发送事件,这个方法会从subscriptionsByEventType这个集合获取订阅的对象CopyOnWriteArrayList<Subscription> 然后遍历这个集合调用postToSubscription这个方法去发送事件,那看下真正发送事件的代码是怎么处理的吧。

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING: //默认的处理流程,即在当前线程处理也是效率最高的方式
                invokeSubscriber(subscription, event);
                break;
            case MAIN: //在主线程调用,通过判断当前的线程是否是主线程,如果当前线程时主线程就直接执行方法,如果当前不是主线程,将方法添加到mainThreadPoster队列中等待执行
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED: //不管当前线程是不是主线程都添加到mainThreadPoster的队列中
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND: //在子线程执行,判断当前是否是在子线程,如果在子线程就直接在当前线程执行,如果不在子线程就添加到backgroundPoster队列等待执行。
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC: //默认添加到异步队列
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }
复制代码

可以看出这个会通过@Subscribe这个注解上的定义的线程进行不同逻辑的处理。真正进行处理事件的是通过invokeSubscriber,其实这个方式很简单,

    void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }
复制代码

看就是通过反射调用方法😄

3.然后在看下粘性事件是在哪里处理的呢

回过来再看下registr中的subscribe方法,在底部有对粘性事件的处理

// ........省略部分代码        
if (subscriberMethod.sticky) {
            if (eventInheritance) {
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }
复制代码

其实很简单,就是判断是否有粘性方法,如果有的话直接遍历将这个事件发送出去,也就是直接invokeSubscriber

在分析下通过eventbus-annotation-processor注解处理器去分析当前的项目将项目下的所有通过@Subscribe注解的方法保存起来。然后需要将前面在gradle中定义的eventBusIndex注册到EventBus中去

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
//然后所有调用EventBus.getDefault()方法获取的是唯一实例
复制代码

可以看通过注解生成器生成的类的源码

public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
    //将所有的注解信息都保存在SUBSCRIBER_INDEX这个Map中
    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(com.bhx.wanandroid.app.ui.EventBusTestActivity.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessageEvent", String.class, ThreadMode.MAIN),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }
    //暴露给查找注解方法的对象通过类对象直接获取SubscriberMethodInfo
    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

复制代码

4.总结

EventBus采用的设计模式有观察者模式(核心),将事件的接受和发送进行分开,建造者模式(EventBusBuilder)进行EventBus的初始化。通过APT技术通过编译器注解生成java文件减少反射的性能销毁。

第一次写文章有什么不对的欢迎指正,谢谢

文章分类
Android
文章标签