android EventBus详解(三)
post()方法调用流程
我们继续来看EventBus
类,的另一个入口方法post()
//已省略部分代码
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
post() 方法首先从 currentPostingThreadState
对象中取了一个 PostingThreadState
,我们来看看这个
currentPostingThreadState 对象的创建代码。
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new
ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
ThreadLocal
是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。其内部原理是通过生成一个它包裹的泛型对象的数组,在不同的线程会有不同的数组索引值,通过这样就可以做到每个线程通过
get() 方法获取的时候,取到的只能是自己线程所对应的数据。
在 EventBus 中, ThreadLocal 所包裹的是一个 PostingThreadState
类,它仅仅是封装了一些事件发送中过程所需的数据。
final static class PostingThreadState {
//通过post方法参数传入的事件集合
final List<Object> eventQueue = new ArrayList<Object>();
boolean isPosting; //是否正在执行postSingleEvent()方法
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
回到 post()
方法,我们看到其核心代码是这句:
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
每次调用post()
的时候都会传入一个事件,这个事件会被加入到队列。而每次执行postSingleEvent()
都会从队列中取出一个事件,这样不停循环取出事件处理,直到队列全部取完。
再看 postSingleEvent() 方法
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
//获取到eventClass所有父类的集合
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) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
//参考sendNoSubscriberEvent注释
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
还记得 EventBusBuild 中的 eventInheritance
是做什么的吗?它表示一个子类事件能否响应父类的onEvent()
方法。
再往下看 lookupAllEventTypes()
它通过循环和递归一起用,将一个类的父类,接口,父类的接口,父类接口的父类,全部添加到全局静态变量 eventTypes
集合中。之所以用全局静态变量的好处在于用全局静态变量只需要将那耗时又复杂的循环+递归方法执行一次就够了,下次只需要通过
key:事件类名 来判断这个事件是否以及执行过 lookupAllEventTypes() 方法。
postSingleEventForEventType()方法
然后我们继续往下,看发送方法 postSingleEventForEventType()
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
//所有订阅了eventClass的事件集合
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//回调subscription的响应方法
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
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;
}
它首先通过这一句
subscriptions = subscriptionsByEventType.get(eventClass);
获取到所有订阅了 eventClass 的事件集合,之前有讲过, subscriptionsByEventType 是一个以 key:订阅的事件 value:订阅这个事件的所有订阅者集合 的 Map 。
最后通过循环,遍历所有订阅了 eventClass 事件的订阅者,并向每一个订阅者发送事件。
看它的发送事件的方法:
postToSubscription(subscription, event, postingState.isMainThread);
噢,又回到了和之前 Subscribe 流程中处理粘滞事件相同的方法里————对声明不同线程模式的事件做不同的响应方法,最终都是通过invokeSubscriber()
反射订阅者类中的以onEvent
开头的方法。
unregister()
我们继续来看EventBus
类,的最后一个入口方法unregister()
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
//取消注册subscriber对eventType事件的响应
unsubscribeByEventType(subscriber, eventType);
}
//当subscriber对所有事件都不响应以后,移除订阅者
typesBySubscriber.remove(subscriber);
}
}
之前讲过typesBySubscriber
key:订阅者对象 value:这个订阅者订阅的事件集合,表示当前订阅者订阅了哪些事件。
首先遍历要取消注册的订阅者订阅的每一个事件,调用unsubscribeByEventType()
,从这个事件的所有订阅者集合中将要取消注册的订阅者移除。最后再以:当前订阅者为
key 全部订阅事件集合为 value 的一个 Map
的 Entry 移除,就完成了取消注册的全部过程。
EventBus工作原理
最后我们再来从设计者的角度看一看EventBus
的工作原理。
订阅的逻辑
1、首先是调用register()方法注册一个订阅者A。
2、遍历这个订阅者A的全部以onEvent
开头的订阅方法。
3、将A订阅的所有事件分别作为 key,所有能响应 key 事件的订阅者的集合作为 value,存入 Map<事件,List<订阅这个事件的订阅者>>
4、以A的类名为 key,所有 onEvent 参数类型的类名组成的集合为 value,存入 Map<订阅者,List<订阅的事件>>。
4.1、如果是订阅了粘滞事件的订阅者,从粘滞事件缓存区获取之前发送过的粘滞事件,响应这些粘滞事件。
发送事件的逻辑
1、取当前线程的发送事件封装数据,并从封装的数据中拿到发送事件的事件队列。
2、将要发送的事件加入到事件队列中去。
3、循环,每次发送队列中的一条事件给所有订阅了这个事件的订阅者。
3.1、如果是子事件可以响应父事件的事件模式,需要先将这个事件的所有父类、接口、父类的接口、父类接口的父类都找到,并让订阅了这些父类信息的订阅者也都响应这条事件。
响应事件的逻辑
1、发送事件处理完成后会将事件交给负责响应的逻辑部分。
2、首先判断时间的响应模式,响应模式分为四种:
PostThread 在哪个线程调用的post()
方法,就在哪个线程执行响应方法。
MainThread 无论是在哪个线程调用的post()
方法,最终都在主线程执行响应方法。
BackgroundThread 无论是在哪个线程调用的post()
方法,最终都在后台线程执行响应方法。(串行执行,一次只执行一个任务,其他任务在队列中处于等待状态)
Async 无论是在哪个线程调用的post()
方法,最终都在后台线程执行响应方法。(并行执行,只要有任务就开一个线程让他执行)
取消注册的逻辑
1、首先是调用unregister()方法拿到要取消注册的订阅者B。
2、从这个类订阅的时候存入的 Map<订阅者,List<订阅的事件>> 中,拿到这个类的订阅事件集合。
3、遍历订阅时间集合,在注册的时候存入的 Map<事件,List<订阅这个事件的订阅者>> 中将对应订阅事件的订阅者集合中的这个订阅者移除。
4、将步骤2中的 Map<订阅者,List<订阅的事件>> 中这个订阅者相关的 Entry 移除。
工作原理图示
android EventBus详解(三)的更多相关文章
- android EventBus详解(一)
EventBus 是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent, Handler, BroadCast 在 Fragment,Activity,Service,线程之间 ...
- (转)android Fragments详解三:实现Fragment的界面
为fragment添加用户界面 fragment一般作为activity的用户界面的一部分,把它自己的layout嵌入到activity的layout中. 一个 要为fragment提供layo ...
- 【转】Android编译系统详解(三)——编译流程详解
原文网址:http://www.cloudchou.com/android/post-276.html 本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 1.概述 编译Androi ...
- Android Fragment详解(三): 实现Fragment的界面
为fragment添加用户界面: Fragment一般作为activity的用户界面的一部分,把它自己的layout嵌入到activity的layout中. 一个 要为fragment提供layout ...
- Android ActionBar详解(三)--->ActionBar的Home导航功能
FirstActivity如下: package cc.testsimpleactionbar2; import android.os.Bundle; import android.app.Activ ...
- android EventBus详解(二)
上一节讲了EventBus的使用方法和实现的原理,下面说一下EventBus的Poster只对粘滞事件和invokeSubscriber()方法是怎么发送的. Subscribe流程 我们继续来看Ev ...
- Android ActionBar详解(三):ActionBar实现切换Tabs标签
实现切换Tabs标签; Activity代码: public class ActionBarTabs extends Activity { @Override protected void onCre ...
- Android 布局详解 -三表格布局(TableLayout)以及重要属性
TableLayout跟TableRow 是一组搭配应用的布局,TableLayout置底,TableRow在TableLayout的上方,而Button.TextView等控件就 ...
- Android Loader详解三:重启与回调
重启装载器 当你使用initLoader()时,如果指定ID的装载器已经存在,则它使用这个装载器.如果不存在呢,它将创建一个新的.但是有时你却是想丢弃旧的然后开始新的数据. 要想丢弃旧数据,你应使用r ...
随机推荐
- shape图形的使用
shape图形的使用 在项目中如果用到有规律的常规的图形,在能够掌握的前提下建议使用shape图形,shape图形相对与图片来说,占用资源更小,并且使用起来不会失真. 效果图 shape图形1 < ...
- Android ListView中Item点击事件失效解决方案
欢迎关注公众号,每天推送Android技术文章,二维码如下:(可扫描) 在平常的开发过程中,我们的ListView可能不只是简单的显示下文本或者按钮,更多的是显示复杂的布局,这样的话,我们就得自己写布 ...
- 电脑hash破解
我一直在想,到底用什么样的方式才能较长时间地控制已经得到了权限的目标呢?留后门,种木马,甚至是Rootkit?留的Webshell痕迹太明显,哪怕是一句话的Webshell,都极容易被管理员清除.放了 ...
- ADO.NET常用方法释义
先列个列表,下面的就是常用的数据库操作的方法. ExecuteNonQuery 释义:对链接执行的SQL语句,并返回受影响的行数(注意:用它来执行目录操作,如查询数据库的结构,创建表等数据库对象,或通 ...
- RH阴性血妇女怀孕注意事项
RH阴性血的妇女怀孕注意事项,本文主要讲解RH阴性血抗体效价检测. 第一.孕前准备:Rh阴性的妇女怀孕前,需要到血液中心或指定医院作ABO和Rh血型鉴定,并且做一次孕前血液免疫学产前检查(血型抗体检 ...
- Spark-streaming 连接flume
1,程序为spark的example中的FlumeEventCount示例 object FlumeEventCount { def main(args: Array[String]) { Strea ...
- Android进阶(九)APP编程感想
从初识Android到现在,在不断做APP(二维码.条形码扫描,彩票购买,火车票余票查询)的过程中,自己学会了很多东西.找时间整理了一下,总结如下: 其中,对于前两个APP,自己都是在他人已完成的基础 ...
- JAVA对象克隆可能会出现的问题
首先,区分一下拷贝和克隆: 拷贝:当拷贝一个变量时,原始变量与拷贝变量引用的是同一个对象.当改变一个变量所引用的对象,则会对另一个变量造成影响. 克隆:当克隆一个对象时,是重新的创建了和该对象内容相同 ...
- iOS中 KVO 键值观察者
KVO Key-Value-Obsever 键值观察者 1.首先要有一个观察者,此时被观察者是自己找一个观察者观察自己的key值对应的value值有没有改变,如果改变了就可以做一些响应的操作 创建一个 ...
- Linux进程实践(5) --守护进程
概述 守护进程是在需要在后台长期运行不受终端控制的进程,通常情况下守护进程在系统启动时自动运行,在服务器关闭的时候自动关闭:守护进程的名称通常以d结尾,比如sshd.xinetd.crond.atd等 ...