EventBus使用以及源码分析
什么是EventBus?
EventBus是一种开源的第三方工具框架,能对我们的项目代码进行高效的解耦,使程序易读性更高代码更简洁;
例如:网络访问,我们一般会先开启一个子线程用于网络访问,收到服务器的返回结果后还需要handler发送到UI线程来更新UI;如果使用EventBus只需要简单的几行代码就能搞定,下面是伪代码:
常规写法
//子线程线程
new Thread(new Runnable(){
......
network connect
......
mainHandler.sendMessage(Message);
}).start();
//ui线程
new Handler(){
handleMessage(){
//update ui
}
}
EventBus加入进去
//ui线程
EventBus.getDefault().register(this); //订阅事件
public void onEventXXX(ParamsType){
//接收到发布事件后执行
}
//子线程
EventBus.getDefault().post(ParamsType);
从上面的代码是不是发现EventBus使用十分方便,而且你发现主线程和子线程没什么特殊的联系,下面就具体讲讲EventBus是如何运行的,先学会如何使用在分析其内部是如何运作的;
EventBus使用方法
EventBus库地址
从上面的伪代码不难看出,EventBus主要分为三个东西构成:
- subscriber 订阅消息的订阅者
- poster 发布消息的发布者
- Message 消息
工作过程:
订阅者订阅消息,发布者发布消息后,订阅者会收到订阅__类型__的消息,进而触发相应的动作;两者可以在同一个线程也可以在不同的线程,这刚好适合我们UI线程执行网络的异步访问,UI线程订阅网络消息,子线程发布消息通知给UI线程,UI线程进而更新UI,大大的减少了两个线程的耦合程度,工作概图如下:
下面以我写的一个秒表demo为例,子线程计时后将秒值传递个主线程来更新UI:
- 使用EventBus,先把它的库加进来
compile 'org.greenrobot:eventbus:3.0.0'
- 编写程序的逻辑代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button)findViewById(R.id.btn);
mTextView = (TextView)findViewById(R.id.display);
mTextView.setText("这是一个计时器");
EventBus.getDefault().register(this); //订阅消息
mTimeThread = new Thread(mTimeTask);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mTimeThread.start();
}
});
}
@Subscribe(threadMode = ThreadMode.MAIN) //收到消息后执行
public void onEventMainThread(Integer integer){
if(mTextView != null){
mTextView.setText(integer+"");
}
}
Runnable mTimeTask = new Runnable() {
int time = 0;
@Override
public void run() {
while(time < 1000){
EventBus.getDefault().post(new Integer(time));//发布消息
time++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this); //取消注册
}
demo下载地址
从上面的代码你需要注意的几点是:
- 使用前必须注册,不使用的时候要取消注册,防止内存泄露
- 现在最新的EventBus只支持注解方式的执行事件,就像我上面onEventMainThread方法的注解
可能看完后你会有这样的疑惑? 子线程的post发布后,主线程是怎么收到消息来执行onEventMainThread方法,这就是EventBus的精妙之处,大致的原理是:
订阅者注册register时,EventBus会扫描注册类的所有方法,并把方法带有@Subscribe的注解方法统计起来,封装到一个Map数据结构中去,map存放了方法Method实例、方法参数类型和订阅者实例;当post发布消息的时候,会把发布的消息的类型与之前map中存放的方法参数类型进行对比,如果类型相同的话,就利用java反射来调用这个方法;注解里面还有一个ThreadMode参数,这个参数表示调用这个方法在哪个线程里面执行,主线程还是非主线程
源码分析
订阅register — EventBus.getDefault().register(this);
查找注册的Method
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
先获取订阅者subscriber的Class对象,然后调用findSubscriberMethods方法查找Class对象的订阅方法,进入findSubscriberMethods方法内部看其实现细节:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
这里,前面5行代码是先去缓存中查看是否有已经缓存好的list,防止多次注册同一个对象;如果没有在调用findUsingReflection和findUsingInfo两个方法,这两个方法都会去调用同一个方法findUsingReflectionInSingleClass方法,查看其内部逻辑:
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass(); //遍历其父类的方法
}
return getMethodsAndRelease(findState);
}
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods(); //这个clazz就是我们的订阅者,获取这个类的自身所有方法
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods(); //获取这个类的所有方法,基类继承接口等
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers(); //获取方法的属性,排除非公有、静态的方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
//这个就是获取方法注解的,只有注解含有Subscribe的才通过
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
//查看是否有添加过这个method,没有添加通过
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
//把最终的method,参数类型,线程mode和优先级封装在SubscriberMethod类里面并添加到findState的一个list里面去
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
上面代码主要完成的工作是:
列出subscriber的所有Method;遍历寻找出非静态,公有的方法并且含有subscribe注解的方法,封装为成为一个SubscriberMethod对象,并且添加都FindState这个类实例的SubscriberMethods的List当中去;
这一步执行完后,你跳回去看方法findUsingReflection,他会调用moveToSuperclass去遍历订阅者父类里面的方法,依次找完全部方法;但这里不会去系统API里面去找的,如java、javax和Android开头的不会去找;
细心的朋友可能还会问,这里只是找出了所有的方法,并没有把方法的List返回回去,返回在这里:
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}
这个方法主要是把之前存在FindState里面的结果(List)SubscriberMethods赋给另外一个引用并把它返回出去;还有这个FindState是个什么东西?FindState是一个查找方法的内部工具类,它里面有4个自己的静态成员,4个成员可以同时工作在多线程中,并且可以重复利用,具有一定的工作效率,省去了中间的销毁创建等工作
封装Method到Map数据结构中
找出所有的Methods后会调用subscribe(subscriber, subscriberMethod)进行method插入数据结构中去,看源码:
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //方法和订阅者封装为一个Subscription
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //根据类型从subscriptionsByEventType(hashmap)拿到它的value,这个value是一个list的索引
//如果value是空的就把新的值添加进去
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);
}
}
//把当前封装好的Subscription插入到list中去,根据优先级;最终post的时候从前面优先级高的先调用
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;
}
}
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
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 {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
subscribe方法主要是一个数据结构的操作过程:
把Subscription加入到list的CopyOnWriteArrayList里面去,在把这个list添加到HashMap的subscriptionsByEventType里面去,记住这个__subscriptionsByEventType__结构体,在post时候也可以用到;可能大家对这个数据的封装、加入其它数据结构有点混乱,下面我用一张图总结下:
到这里注册部分就完成,实际过程更新一个查找方法,存储到一个HashMap中去,这个最终的HashMap叫做subscriptionbyEventType,记住这个,在后面post发布消息的时候还会用