一文彻底搞懂EventBus 3,2024年最新字节跳动公司面试流程

先看图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再看代码

public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass(); // 获取传入的要注册类的字节码文件
List subscriberMethods =
subscriberMethodFinder.findSubscriberMethods(subscriberClass); // ->>分析1

synchronized (this) {

// 遍历订阅方法封装类的集合
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod); // ->> 分析4
}
}
}

从上面的图可以看出,这个方法其实就是做了2件事

  1. 根据注册类的字节码文件,调用findSubscriberMethods方法,获取该注册类上的所有订阅方法的信息集合。
  2. 遍历这个信息集合,给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<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。 完事了。

4.unregister取消注册

使用很简单

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 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 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 subscriptions;
synchronized (this) {

// 还记得注册时构建的map subscriptionsByEventType吗?对,这步就是根据事件类型,获取它所对应的List也就是订阅方法集合
subscriptions = subscriptionsByEventType.get(eventClass);
}

// 如果集合不为空
if (subscriptions != null && !subscriptions.isEmpty()) {

// 遍历集合,取出Subscription(订阅方法信息包装类)
for (Subscription subscription : subscriptions) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

感谢您的阅读,在文末给大家准备一个福利。本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
一个架构设计,我都有一一的对应笔记为你讲解。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

[外链图片转存中…(img-37QrjUUb-1712800167392)]

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-bvIJSOBU-1712800167392)]

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值