EventBus的源码详解与架构分析,使用EventBus会造成什么弊端,带你复现整个思路历程
本专栏专注分享大型Bat面试知识,后续会持续更新,喜欢的话麻烦点击一个star
面试官: 为什么会用到EventBus,EventBus的源码详解与架构分析,使用EventBus会造成什么弊端
心理分析:eventbus对程序员相爱相恨,爱 爱在太方便了,恨 恨在对程序的可读性有致命的伤害,小公司会用但面试的时候不会考,大公司不会用,但面试的时候使劲考。从该文中我们可以找到如何面试上大公司的线索和机会
求职者: 求职者应该从 eventbus弊端入手,然后深入源码入手,告诉面试官 我不仅用过 还深入研究过。最后因为他的可读性 抛弃了它,所以这才是最厉害的
EventBus 需要解决的问题
在日常编码里面,我们会遇到很多网络请求,数据库操作等等,一般情况下这些操作都是通过观察者模式来实现的。
通过Volley来简单举个例子:
ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap bitmap) { mImageView.setImageBitmap(bitmap); } }, 0, 0, null, new Response.ErrorListener() { public void onErrorResponse(VolleyError error) { mImageView.setImageResource(R.drawable.image_load_error); } });
此时,你会发现并且开始思考一个问题,如果很多观察者模式需要使用了?
比如,你正在开发一个东西,需要监听网络状态变化,App的安装情况,内容的下载情况。
当存在很多观察者模式,「如何将这些事件通知到监听者」是可以复用的模块,这就是EventBus存在的意义。这里需要大家想明白一个问题,观察者模式本身就是一个可以复用的模块。
- 内容下载模块
- 电量监听模块
- App按照通知
他们都可以通过EventBus将自身的事件发布出去,使用者只需要在这个模块里面,注册对于自己感兴趣的内容就行。
EventBus 带来的好处和引入的问题
-
好处比较明显,就是独立出一个发布订阅模块,调用者可以通过使用这个模块,屏蔽一些线程切换问题,简单地实现发布订阅功能。
-
坏处可能比较隐晦,但这些需要足够引起我们的重视
-
大量的滥用,将导致逻辑的分散,出现问题后很难定位。 没办法实现强类型,在编译的时候就发现问题,(Otto实现了这个,但性能有问题)。在实现上通过一个很弱的协议,比如onEvent{XXX}, {XXX}表示ThreadModel,来实现线程的切换。后面在代码解析的时候,会说明这个问题。
-
代码可读性有些问题,IDE无法识别这些协议,对IDE不友好。 总得来说,如果项目里面有大量的事件交互,那么还是可以通过EventBus来实现,否则还是推荐自己在模块内部实现观察者模式
EventBus 源码解析
EventBus.java
源码阅读从外观类开始,这里是 EventBus.java,核心接口都在这个类里面实现,对内容感兴趣的调用方使用 register 方法,当有事件产生的时候,会在onEvent的时候收到相应的回调。
register(Object object);
registerSticky(Object object);
unRegister(Object object);
post(Object object);
先看看初始化部分,看看如何实现单例的(可选的)。
// volatile 这里是需要重视的,这个关键字保证了defaultInstance在不同线程间的可见性,也就是说在多线程环境下,看到的仍然是最新修改的值。
static volatile EventBus defaultInstance; /** Convenience singleton for apps using a process-wide EventBus instance. */ public static EventBus getDefault() { // 这一步不存在线程问题,volatile保证了。如果没有defaultInstance实例化出来, if (defaultInstance == null) { synchronized (EventBus.class) { // 进入同步块的时候,不能保证defaultInstance没有被实例化出来,所以需要进行double-check if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; } // 这里实现的时候,考虑的是defaultInstance 不一定是每个人都需要创建的,否则没必要使用lazy的实现方式 // 下面是一种实现方式 static { defaultInstance = new EventBus(); }
EventBus实现了EventBusBuilder,通过Builder的方式使得构建的时候更加容易
public static EventBusBuilder builder() { return new EventBusBuilder(); }
下面重点看看register(Object subscriber, boolean sticky, int priority)
方法
private synchronized void register(Object subscriber, boolean sticky, int priority) { // 用 subscriberMethodFinder 提供的方法,找到在 subscriber 这个类里面,订阅的内容。 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass()); for (SubscriberMethod subscriberMethod : subscriberMethods) { // 遍历这些方法,subscribe 这些事件 subscribe(subscriber, subscriberMethod, sticky, priority); } }
findSubscriberMethods 这个方法是实现 EventBus 的核心代码,这里面包含了 EventBus 隐式定义的交互协议。从这个方法里面,可以看到如何争取地使用EventBus。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { String key = subscriberClass.getName(); List<SubscriberMethod> subscriberMethods; // 如果这个 Class 对应的方法被缓存,直接返回。 synchronized (methodCache) { subscriberMethods = methodCache.get(key); } // 这个方法其实可以放在 前面的 synchronized 模块里面 if (subscriberMethods != null) { return subscriberMethods; } subscriberMethods = new ArrayList<SubscriberMethod>(); Class<?> clazz = subscriberClass; HashSet<String> eventTypesFound = new HashSet<String>(); StringBuilder methodKeyBuilder = new StringBuilder(); while (clazz != null) { String name = clazz.getName(); // 跳过JDK里面的类 if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) { // Skip system classes, this just degrades performance break; } // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again) // 获取所有声明的方法 Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { String methodName = method.getName(); if (methodName.startsWith(ON_EVENT_METHOD_NAME)) { int modifiers = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { // 方法是 Public 的 Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length()); ThreadMode threadMode; // 方法的前缀是否是 ‘OnEvent’, 如果是‘OnEvent’,查看后面的字符串,这里定义了 4 种基本类型 // ThreadModel 会在后面介绍 if (modifierString.length() == 0) { threadMode = ThreadMode.PostThread; } else if (modifierString.equals("MainThread")) { threadMode = ThreadMode.MainThread; } else if (modifierString.equals("BackgroundThread")) { threadMode = ThreadMode.BackgroundThread; } else if (modifierString.equals("Async")) { threadMode = ThreadMode.Async; } else { if (skipMethodVerificationForClasses.containsKey(clazz)) { continue; } else { throw new EventBusException("Illegal onEvent method, check for typos: " + method); } } // 获取参数类型 Class<?> eventType = parameterTypes[0]; methodKeyBuilder.setLength(0); methodKeyBuilder.append(methodName); methodKeyBuilder.append('>').append(eventType.getName()); // 得到类似于一个句柄的东西,比如 onEventMainThread>DownloadInfo String methodKey = methodKeyBuilder.toString(); if (eventTypesFound.add(methodKey)) { // Only add if not already found in a sub class subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType)); } } } else if (!skipMethodVerificationForClasses.containsKey(clazz)) { Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "." + methodName); } } } // 这里要为 EventBus 点个赞了,EventBus 是支持继承的 clazz = clazz.getSuperclass(); } if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called " + ON_EVENT_METHOD_NAME); } else { synchronized (methodCache) { methodCache.put(key, subscriberMethods); } return subscriberMethods; } }
现在看下如何把 subscriberClass 里面的内容订阅到 EventBus 里面去。
// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) { Class<?> eventType = subscriberMethod.eventType; // 获取订阅了某种类型数据的 Subscription 。 使用了 CopyOnWriteArrayList ,这个是线程安全的, // CopyOnWriteArrayList 会在更新的时候,重新生成一份 copy,其他线程使用的是 // copy,不存在什么线程安全性的问题。 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<Subscription>(); subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again) // subscriberMethod.method.setAccessible(true); int size = subscriptions.size(); for (int i = 0; i <= size; i++) { // 根据优先级进行插入,其实这里可以替换为优先级队列的 if (i == size || newSubscription.priority > subscriptions.get(i).priority) { subscriptions.add(i, newSubscription); break; } } List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<Class<?>>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (sticky) { // 是否支持继承,这个可以在 Builder 的时候指定,如果不支持,那么可能有20%以上的性能提升 if (eventInheritance) { // Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure should be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>). 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 { // 检查是否有 sticky 的event, 如果存在就发布出去 Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
EventBus ThreadModel EventBus 一共提供了 4 种 ThreadModel,
分别是
PostThread,
MainThread,
BackgroundThread,
Async
PostThread 默认实现,执行发生在同一个线程 MainThread 执行在UI 线程上 BackgroundThread 回调发生在非 UI 线程上 Async 永远执行在一个其他的线程上 以上这四种类型,足以支持观察者模式里面需要进行的异步处理。
EventBus 如何实现线程转换的
但凡经历一些实际项目,就会发现,经常存在「生产」和「消费」冲突的情况,这里就需要使用「生产者与消费者」模式。
EventBus 中 生产者和消费者模式的实现主要是在 PendingPostQueue里面。
PendingPostQueue 的实现比较简单,主要是通过在 enqueue 和 poll 的时候进行 synchronized 同步来实现的。
synchronized void enqueue(PendingPost pendingPost) { if (pendingPost == null) { throw new NullPointerException("null cannot be enqueued"); } // 将 Post 插入到队列尾部 if (tail != null) { tail.next = pendingPost; tail = pendingPost; } else if (head == null) { // 在最开始的时候,建立头部和尾部的索引 head = tail = pendingPost; } else { throw new IllegalStateException("Head present, but no tail"); } notifyAll(); } synchronized PendingPost poll() { PendingPost pendingPost = head; // 从头部获取 if (head != null) { head = head.next; if (head == null) { tail = null; } } return pendingPost; }
// 这里需要注意的地方是 PendingPost, 这里维护了一个 pendingPostPool 的池子, 当PendingPost 不再需要的时候,就释放回池子里面去,避免了新建对象的开销。 static void releasePendingPost(PendingPost pendingPost) { pendingPost.event = null; pendingPost.subscription = null; pendingPost.next = null; synchronized (pendingPostPool) { // Don't let the pool grow indefinitely if (pendingPostPool.size() < 10000) { pendingPostPool.add(pendingPost); } } }
EventBus 如何发布事件的
// 每个ThreadModel (除了PostThread) 都维护了一个 Poster, 这个Post 里面维持了一个 ``生产者消费者模式``, 来消费和使用事件。 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case PostThread: invokeSubscriber(subscription, event); break; case MainThread: // 主线程的poster if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case BackgroundThread: 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); } }
关于我
更多信息可以点击关于我 , 非常希望和大家一起交流 , 共同进步
原文地址:https://www.cnblogs.com/1157760522ch/p/11558019.html
- android Native堆
- Spring Boot中Web应用的统一异常处理
- android bitmap的内存分配和优化
- Gitflow工作流程
- Android学习第一弹:Android通过用代码画虚线椭圆边框背景来学习一下shape的用法
- Spring Boot中使用Swagger2构建强大的RESTful API文档
- iOS iOS与html进行交互
- Android学习第三弹之Android图片颜色处理
- C#Winform使用扩展方法自定义富文本框(RichTextBox)字体颜色
- Android如何实现毛玻璃效果之Android高级模糊技术
- Android Loader详解
- Gridview的item含有checkbox,setOnItemClickListener方法失效的问题
- 有效解决Android加载大图片内存溢出的问题
- 第七章 正则表达式编程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法