一文彻底了解EventBus3原理
1.简介 【一文彻底了解EventBus3原理】EventBus是一种用于Android的事件发布-订阅总线框架,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
2.Subscribe注解 自3.0开始,订阅事件的方法开始使用了Subscribe注解,不再使用方法名了,如以下方式
@Subscribe
public void testEventBus(Object obj){
...
}
看下注解
@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可以指定的模式有:
- ThreadMode.POSTING:默认的线程模式,在哪个线程发送事件就在对应线程处理事件,避免了线程切换,效率高。
- ThreadMode.MAIN:如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
- ThreadMode.MAIN_ORDERED:无论在哪个线程发送事件,都将事件加入到队列中,然后通过Handler切换到主线程,依次处理事件。
- ThreadMode.BACKGROUND:与ThreadMode.MAIN相反,如果在子线程发送事件,则直接在子线程处理事件;如果在主线程上发送事件,则先将事件入队列,然后通过线程池处理事件。
- ThreadMode.ASYNC:与ThreadMode.MAIN_ORDERED相反,无论在哪个线程发送事件,都将事件加入到队列中,然后通过线程池执行事件
3.register注册 好了,要想使用Eventbus,则要先注册它,看看如何使用
EventBus.getDefault().register(this);
很简单吧,getDefault()其实就是一个单例模式,创建EventBus实例对象,并返回
public static EventBus getDefault() {if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
没啥可说的,继续看register
先看图
文章图片
再看代码
public void register(Object subscriber) {
Class> subscriberClass = subscriber.getClass();
// 获取传入的要注册类的字节码文件
List> subscriberMethods =
subscriberMethodFinder.findSubscriberMethods(subscriberClass);
// ->>分析1synchronized (this) {// 遍历订阅方法封装类的集合
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
// ->> 分析4
}
}
}
从上面的图可以看出,这个方法其实就是做了2件事
- 根据注册类的字节码文件,调用findSubscriberMethods方法,获取该注册类上的所有订阅方法的信息集合。
- 遍历这个信息集合,给2个map填充数据:
subscriptionsByEventType可以根据event(事件类型,订阅方法上的参数类型)获取所有订阅方法信息集合。
typesBySubscriber可以根据这个注册类,获取这个注册类上所有的event事件类型。
/**
* 分析1:findSubscriberMethods()
* 作用:获取当前要进行注册类中的所有订阅方法,也就是找寻使用了Subscribe注解、有public修饰符、一个参数的方法
*/
List> findSubscriberMethods(Class> subscriberClass) {// METHOD_CACHE: 是一个ConcurrentHashMap,key是要注册类的字节码文件,value是这个字节码文件里的所有订阅方法信息的集合,集合的元素是SubscriberMethod,它实际上就是订阅方法的信息类,包含Method对象、线程模式、事件类型、优先级、是否是粘性事等。
List> 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> 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并返回,最后释放掉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> 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> 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。 完事了。
4.unregister取消注册 使用很简单
EventBus.getDefault().unregister(this);
看图
文章图片
看下代码
public synchronized void unregister(Object subscriber) {// ->> 分析6
List> 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> 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中找到,这个事件所对应的订阅方法的集合,再遍历集合,判断该订阅方法的注册类信息,是否是要解除绑定的注册类,如果是,移除该订阅方法信息,完成解除绑定。
4.post发布事件 使用也很简单
EventBus.getDefault().post(new Object());
看图
文章图片
看代码
public void post(Object event) {// ->> 分析8
PostingThreadState postingState = currentPostingThreadState.get();
// 获取postingState里面存的一个队列
List
分析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> 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> subscriptions;
synchronized (this) {// 还记得注册时构建的map subscriptionsByEventType吗?对,这步就是根据事件类型,获取它所对应的List也就是订阅方法集合
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与它类似,但是里面没有同步锁,每次执行都会新开辟一个子线程去执行任务。
5.Sticky粘性事件 什么是粘性事件?一般来说,我们使用 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, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, 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就分析完毕了,下面一张大图,来加深印象
6.看图
文章图片
7.最后想说的 其实这个eventbus在所有开源项目中,是属于那种比较经典的,里面设计的很巧妙,有兴趣的小伙伴们可以手动写一个eventbus。另外在这个留个小思考
eventbus支不支持跨进程?为什么?知道的小伙伴可以在下面留言8.下集预告 让我们自己手动撸一个Butterknife
推荐阅读
- 我们重新了解付费。
- 拍照一年啦,如果你想了解我,那就请先看看这篇文章
- C语言中的时间函数clock()和time()你都了解吗
- 操作系统|[译]从内部了解现代浏览器(1)
- 生发知识,带你深入了解
- 了解自然大气粒子对气候的影响
- 陷入父母的心理战,孩子被彻底打败
- 【图解】9张图彻底搞懂堆排序
- 带你了解类型系统以及flow和typescript的基本使用
- 窝在家里,你闷坏了吗(了解这12个假设,给自己的心理增加免疫力)