EventBus是一种用于Android的事件发布-订阅总线框架,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通讯的复杂度,尤为是碎片之间进行通讯的问题,能够避免因为使用广播通讯而带来的诸多不便。java
自3.0开始,订阅事件的方法开始使用了Subscribe注解,再也不使用方法名了,如如下方式git
@Subscribe
public void testEventBus(Object obj){
...
}
复制代码
看下注解github
@Documented
@Retention(RetentionPolicy.RUNTIME) // 注解不只被保存到class文件中,jvm加载class文件以后,仍然存在
@Target({ElementType.METHOD}) // 做用在方法上
public @interface Subscribe {
// 指定事件订阅方法所在的线程模式,也就是决定订阅方法是在哪一个线程,默认是POSTING模式
ThreadMode threadMode() default ThreadMode.POSTING;
// 是否支持粘性事件
boolean sticky() default false;
// 优先级,若是指定了优先级,则若干方法接收同一事件时,优先级高的方法会先接收到。
int priority() default 0;
}
复制代码
ThreadMode能够指定的模式有:缓存
好了,要想使用Eventbus,则要先注册它,看看如何使用框架
EventBus.getDefault().register(this);
复制代码
很简单吧,getDefault()其实就是一个单例模式,建立EventBus实例对象,并返回jvm
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
复制代码
没啥可说的,继续看registerasync
先看图ide
再看代码post
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass(); // 获取传入的要注册类的字节码文件
List<SubscriberMethod> subscriberMethods =
subscriberMethodFinder.findSubscriberMethods(subscriberClass); // ->>分析1
synchronized (this) {
// 遍历订阅方法封装类的集合
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod); // ->> 分析4
}
}
}
复制代码
从上面的图能够看出,这个方法其实就是作了2件事ui
/** * 分析1:findSubscriberMethods() * 做用:获取当前要进行注册类中的全部订阅方法,也就是找寻使用了Subscribe注解、有public修饰符、一个参数的方法 */
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// METHOD_CACHE: 是一个ConcurrentHashMap,key是要注册类的字节码文件,value是这个字节码文件里的全部订阅方法信息的集合,集合的元素是SubscriberMethod,它实际上就是订阅方法的信息类,包含Method对象、线程模式、事件类型、优先级、是不是粘性事等。
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); // 这步实际上就是看看这个注册类的方法是否已经缓存了,缓存过就直接根据类返回
if (subscriberMethods != null) {
return subscriberMethods;
}
// EventBus是支持EventBusBuilder的,若是咱们自定义了EventBusBuilder,则ignoreGeneratedIndex为true,不然为false,咱们没自定义,全部看false
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// ->>分析2
subscriberMethods = findUsingInfo(subscriberClass);
}
// 若是该类没有找到订阅方法,抛出异常
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation");
} else {
// 将该注册类的类型为key, 将这个类全部注册方法的封装类集合为value存入map集合
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
// ->> 返回register()方法中
}
}
复制代码
这个方法主要做用就是根据传入的注册类返回该类上全部的订阅方法的信息,先找缓存METHOD_CACHE,有就走缓存,没有就调用findUsingInfo方法获取订阅方法信息集合,而后再根据注册类为key, 订阅方法的信息集合为value, 存入缓存(METHOD_CACHE)中。
/** * 分析2:findUsingInfo() * 做用:若是findState缓存了,订阅方法信息,则使用findState里的缓存,不然调用findUsingReflectionInSingleClass方法,反射获取订阅方法信息。 */
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
// FindState辅助咱们查找订阅方法的类,后面会讲述
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
// findState.clazz就是咱们的注册类subscriberClass
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState);
// 该类第一次注册时,findState.subscriberInfo为null, 咱们走false
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 {
// ->> 分析3
findUsingReflectionInSingleClass(findState);
}
// 修改findState.clazz为subscriberClass的父类Class,即须要遍历父类
findState.moveToSuperclass();
}
// 将查找到的方法保存在了FindState实例的subscriberMethods集合中。而后使用subscriberMethods构建一个新的List<SubscriberMethod>并返回,最后释放掉findState
return getMethodsAndRelease(findState);
// ->> 返回到findSubscriberMethods() 方法中
}
复制代码
/** * 分析3:findUsingReflectionInSingleClass() * 做用:经过反射获取订阅方法的信息 */
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 经过反射获取订阅类中的全部方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
...
}
// 遍历方法
for (Method method : methods) {
// 获取方法修饰符
int modifiers = method.getModifiers();
// 方法是public类型,但非abstract、static等
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
// 获取方法的修饰类型
Class<?>[] parameterTypes = method.getParameterTypes();
// 只能是1个参数
if (parameterTypes.length == 1) {
// 获取方法上的名为Subscribe的注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
// 若是该方法带Subscribe注解
if (subscribeAnnotation != null) {
// 获取该订阅方法上的第一个参数类型,也就是订阅的事件类型
Class<?> eventType = parameterTypes[0];
// checkAdd()方法用来判断FindState中是否已经添加过将该事件类型为key的键值对,没添加过则返回true
if (findState.checkAdd(method, eventType)) {
// 获取线程模式
ThreadMode threadMode = subscribeAnnotation.threadMode();
// 将该订阅方法,事件类型,线程模式,优先级,是否支持粘性事件等信息,封装成SubscriberMethod对象,并添加到findState中的subscriberMethods集合里
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
// ->> 返回到findUsingInfo() 方法中
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
...
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
...
}
}
}
复制代码
根据反射,获取订阅方法的信息数据,而后将它分封装成SubscriberMethod对象,并添加到findState的集合中。
/** * 分析4:subscribe() * 做用:主要就是构建2个map对象 */
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 获取该订阅方法的事件类型
Class<?> eventType = subscriberMethod.eventType;
// 将订阅方法的封装类,再进行封装,也就是注册类的信息也存入了
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// subscriptionsByEventType是hashmap, 以事件类型为key, Subscription集合为value
// 先查找subscriptionsByEventType是否存在以当前事件类型为key的值
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// 若是没有的话
if (subscriptions == null) {
// 建立集合,根据事件类型,合并数据
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType);
}
}
// 添加上边建立的newSubscription对象到subscriptions中
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
// 根据优先级进行排序
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// typesBySubscriber也是一个HashMap,保存了以当前要注册类的对象为key,注册类中订阅事件的方法的参数类型的集合为value的键值对
// 和上面同样,根据key先判断,是否已经存储过了,若是已经存储过了,直接取出订注册类中订阅事件的方法的参数类型的集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// 是否支持粘性事件
if (subscriberMethod.sticky) {
// ->> 分析5
...
}
复制代码
2个map构建完毕了,咱们的注册也就完事了
传入注册类信息,根据反射获取注册类上的全部方法,遍历这些方法,取出其中的订阅方法(条件是,一个参数,权限为public,使用了Subscribe标签)将方法的信息封装成SubscriberMethod对象,并存入集合,而后再遍历这个集合,取出其中的SubscriberMethod对象,再根据注册类的字节码文件,合并成Subscription对象,再根据event类型,进行从新分类,存入map subscriptionsByEventType中(key 为event, value 为List),再建立map typesBySubscriber, 注册类为key , list为value。 完事了。
使用很简单
EventBus.getDefault().unregister(this);
复制代码
看图
看下代码
public synchronized void unregister(Object subscriber) {
// ->> 分析6
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
// 若是集合不为null
if (subscribedTypes != null) {
// 遍历集合,获取订阅事件的类型
for (Class<?> eventType : subscribedTypes) {
// ->> 分析7
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
复制代码
分析6:还记得咱们分析注册时,建立的那2个map吗? 其中一个是typesBySubscriber,key是注册类,value是事件类型的集合(List), 这一步就是根据注册类获取该类全部订阅方法的事件类型。
/** * 分析7 */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
// 根据事件类型,获取该事件类型所对应的订阅方法信息的集合
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// 若是集合不为null
if (subscriptions != null) {
// 遍历,该事件类型所对应的订阅方法
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
// 获取Subscription对象,该对象包含了订阅方法的全部信息和注册类信息
Subscription subscription = subscriptions.get(i);
// 由于subscriptionsByEventType可不光包含了1个注册类的信息,因此要加下面的判读,若是该订阅方法所在的注册类是咱们要解除的注册类的话
if (subscription.subscriber == subscriber) {
subscription.active = false;
// 从集合中,将该订阅方法的信息删除掉
subscriptions.remove(i);
i--;
size--;
}
}
}
}
复制代码
解除绑定,其实比较简单,主要就是运用注册时所产生的2个map, 先根据typesBySubscriber,也就是根据要解除绑定的注册类,找到这个类所拥有的全部订阅事件,而后遍历这些订阅事件,再根据这些订阅事件,在subscriptionsByEventType中找到,这个事件所对应的订阅方法的集合,再遍历集合,判断该订阅方法的注册类信息,是不是要解除绑定的注册类,若是是,移除该订阅方法信息,完成解除绑定。
使用也很简单
EventBus.getDefault().post(new Object());
复制代码
看图
看代码
public void post(Object event) {
// ->> 分析8
PostingThreadState postingState = currentPostingThreadState.get();
// 获取postingState里面存的一个队列
List<Object> eventQueue = postingState.eventQueue;
// 将要发送的事件,存入队列中
eventQueue.add(event);
// 判断该事件是否正在发送,若是在发送,则跳过下面的逻辑
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()) {
// ->> 分析9
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
// 重置状态
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
复制代码
分析8:postingState实际就是一个线程状态的封装类,包含事件队列,线程状态,是否正在发送的标识位,Subscription等信息,currentPostingThreadState为ThreadLocal,这也就说明postingState为线程独有的,不会让其余线程共享当前线程的数据
post() 方法主要就是要先将发送的事件保存在postingState中的队列里面,它是线程独有的,而后经过循环队列,将事件交给postSingleEvent()方法处理。
/** * 分析9 */
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// 是否要查看全部的继承关系
if (eventInheritance) {
// 经过lookupAllEventTypes()拿到该事件全部的父类事件类型
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
// 遍历事件类型
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
// ->> 分析10
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);
}
// 若是咱们没有订阅事件,则发送NoSubscriberEvent
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
复制代码
postSingleEvent()方法中,根据eventInheritance属性,决定是否向上遍历事件的父类型,而后用postSingleEventForEventType()方法进一步处理事件。
/** * 分析10 */
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 还记得注册时构建的map subscriptionsByEventType吗?对,这步就是根据事件类型,获取它所对应的List<subscription>也就是订阅方法集合
subscriptions = subscriptionsByEventType.get(eventClass);
}
// 若是集合不为空
if (subscriptions != null && !subscriptions.isEmpty()) {
// 遍历集合,取出Subscription(订阅方法信息包装类)
for (Subscription subscription : subscriptions) {
// 记录事件
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
// 处理事件 ->> 分析11
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
复制代码
这个方法其实很简单,就是根据事件类型,在subscriptionsByEventType中找到对应的订阅方法信息的集合,而后遍历集合,拿到订阅方法信息的封装类,调用postToSubscription去执行。
/** * 分析11 */
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
// 根据订阅方法设置的线程模式去执行
switch (subscription.subscriberMethod.threadMode) {
// 默认线程模式,在哪一个线程发送事件,就在哪一个线程接收事件
case POSTING:
// ->> 分析12
invokeSubscriber(subscription, event);
break;
// 若是是主线程,则直接执行,子线程加入队列,而后经过 Handler 切换到主线程执行
case MAIN:
if (isMainThread) {
// 主线程,直接反射执行
invokeSubscriber(subscription, event);
} else {
// ->> 分析13
mainThreadPoster.enqueue(subscription, event);
}
break;
// 不管在哪一个线程,都加队列,经过handler 在主线程执行
case MAIN_ORDERED:
// ->> 分析13
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
// 若是在子线程中,直接执行,若是在主线程中,加入队列,经过线程池执行
case BACKGROUND:
if (isMainThread) {
// ->> 分析15
backgroundPoster.enqueue(subscription, event);
} else {
// 在子线程,直接反射执行
invokeSubscriber(subscription, event);
}
break;
// 不管在哪一个线程执行,都加入队列,用线程池执行
case ASYNC:
// AsyncPoster和backgroundPoster类型,可是AsyncPoster没有加同步锁,这也就形成了,它每次执行一个任务,都会开一个子线程,而backgroundPoster不会
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
/** * 分析12:直接经过反射调用执行 */
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);
}
}
/** * 分析13:mainThreadPoster为HandlerPoster, 具体分析下HandlerPoster */
public class HandlerPoster extends Handler implements Poster {
private final PendingPostQueue queue;
private boolean handlerActive;
......
public void enqueue(Subscription subscription, Object event) {
// 用subscription和event封装一个PendingPost对象
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
// 加入到队列中
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
// sendMessage()发送处理事件的消息,handleMessage()方法将被执行,将子线程切换到主线程
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
// 遍历队列
while (true) {
// 出队列,取出PendingPost对象
PendingPost pendingPost = queue.poll();
...
// ->> 分析14
eventBus.invokeSubscriber(pendingPost);
...
}
} finally {
handlerActive = rescheduled;
}
}
}
/** * 分析14:进一步处理PendingPost对象 */
void invokeSubscriber(PendingPost pendingPost) {
// 取出事件类型
Object event = pendingPost.event;
// 取出订阅方法的信息封装类
Subscription subscription = pendingPost.subscription;
// 释放pendingPost引用的资源
PendingPost.releasePendingPost(pendingPost);
if (subscription.active) {
// 经过反射调用执行该订阅方法
invokeSubscriber(subscription, event);
}
}
/** * 分析15 */
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
// 用subscription和event封装一个PendingPost对象
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
// 加入队列
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
// 调用newCachedThreadPool线程池,执行任务
eventBus.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
// 循环队列
while (true) {
// 等待1秒,取出PendingPost对象
PendingPost pendingPost = queue.poll(1000);
...
// ->> 分析14(在上面)
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
}
复制代码
post也不难,首先是将发送的事件保存在postingState中的队列里面,它是线程独有的,而后遍历postingState中的事件队列,拿出该线程下,全部的事件的集合,而后遍历它,再根据subscriptionsByEventType,取出该事件所对应的全部订阅方法,而后看是否可以直接处理,若是能,直接反射调用订阅方法,若是不能,直接经过HandlerPower、BackgroundPower、AsyncPower切换线程后,再进行反射调用处理。
其中HandlerPower内部就直是封装了个Handler,每次调用的时候,先将事件加入到队列中,而后根据Handler切换到主线程,按顺序取出队列中的事件,反射执行 BackgroundPower是封装了catchThreadPool用于执行任务, AsyncPower与它相似,可是里面没有同步锁,每次执行都会新开辟一个子线程去执行任务。
什么是粘性事件?通常来讲,咱们使用 EventBus 都是先准备好订阅事件的方法,而后注册事件,最后在发送事件,即要先有事件的接收者。但粘性事件却偏偏相反,咱们能够先发送事件,后续再准备订阅事件的方法、注册事件。
先看下如何使用,其实很简单
// 发布事件
EventBus.getDefault().postSticky(new Object());
// 订阅事件
@Subscribe(sticky = true)
public void testEventBus(Object obj){
...
}
复制代码
事件都发送了,再注册订阅方法居然还能接收到以前的事件,它是怎么作到的? 看图
图在下面
看代码
public void postSticky(Object event) {
// 很简单,将要发布的粘性事件的类型和对应事件,存入map stickyEvents中
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// 这个就是一个普通的发布事件,上文分析过了
post(event);
}
复制代码
咱们将发布的粘性事件的类型和对应事件存入了咱们的map中,那么咱们是在哪里执行的尼? 还记得咱们上面写的分析5吗?我把代码补全下,在注册方法中
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 构建那两个很重要的map
...
// 若是该事件支持粘性事件的话
if (subscriberMethod.sticky) {
// 若是须要向上查找事件的父类
if (eventInheritance) {
// 遍历咱们上面存储的粘性事件的集合,取出里面存储的粘性事件
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
// 若是candidateEventType是eventType的子类
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
// ->> 分析16
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
/** * 分析16 */
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
// 若是该方法支持粘性事件
if (stickyEvent != null) {
// 上面分析过这个方法,根据线程模式,去处理事件
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
复制代码
看,是否是很简单,来咱们总结下
若是须要发送粘性事件的话,在发送的时候,会将粘性事件的事件类型和对应事件存储到map stickyEvents中,等新的注册类进行注册的时候,若是有的订阅方法支持粘性事件,则会在注册的时候,取出stickyEvents里面的存储粘性事件,而后遍历处理事件。
好了,eventbus就分析完毕了,下面一张大图,来加深印象
其实这个eventbus在全部开源项目中,是属于那种比较经典的,里面设计的很巧妙,有兴趣的小伙伴们能够手动写一个eventbus。另外尼,在这个再留个小思考
eventbus支不支持跨进程?为何?知道的小伙伴能够在下面留言
让咱们本身手动撸一个Butterknife