前言
提起EventBus,大概只要会android的人,就算没有用过也会听过。是android端有名的组件之间互相通信的框架,并且使用起来及其简单,只要10分钟就能快速上手。
但是你们在使用的时候有木有好奇,它究竟是怎么实现事件的传递的。不管你们有木有,反正我是有。所以我决定来看一下EventBus的源码,但是要我直接看EventBus最新的3.0的源码?
你见过玩游戏,一上来就打boss的?所以 像我这样的菜鸡,还是去看EventBus1.0的源码吧!
EventBus的使用
在观察源码之前,我想还是先来介绍一下EventBus1.0的使用。
下载地址
https://github.com/greenrobot/EventBus/releases
在这里,我们只需要去下载那个远古时代的1.0就可以了
导入工程
由于EventBus1.0的包,还是使用eclipse开发的。所以你又两个方法将他导入到我们的工程
1.先把工程转换为As工程,然后再将其作为module导入我们工程即可
2.直接将eclipse工程作为module导入
由于我比较傻白甜,用的第一种方法,花了我半个小时。。。。。。
但是也实现了目标,所以你们大家自己决定吧
使用EventBus
首先大家来看下我的目录结构。
然后源码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
}
public void onEvent(AnyEventType event) {
Log.d(TAG, "onEvent: 我收到消息的线程"+Thread.currentThread().getName());
Log.d(TAG, "onEvent: 收到消息"+event.getText());
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
public void click(View view){
startActivity(new Intent(this,SecondActivity.class));
}
}
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
public void click(View view){
new Thread(){
@Override
public void run() {
super.run();
senMsg();
}
}.start();
}
private void senMsg() {
Log.d("MainActivity", "run: 我运行在线程"+Thread.currentThread().getName());
EventBus.getDefault().post(new AnyEventType("这是第二个页面传递过来的"));
}
}
public class AnyEventType {
private String text;
public AnyEventType(String text) {
this.text=text;
}
public String getText() {
return text;
}
}
ok,由于代码太过简单,这里就不在赘述。
源码解析
订阅
这次我们看源码,就按照我们的使用EventBus的顺序来吧。
我们在使用EventBus的第一件事,就是订阅事件。
EventBus.getDefault().register(this);
首先EventBus.getDefault()
private static final EventBus defaultInstance = new EventBus();
public static EventBus getDefault() {
return defaultInstance;
}
EventBus全局使用一个对象,并且作为静态变量,在应用开始的时候就被创建。
这里太简单,我们接着往下看看register源码
public enum ThreadMode {
/** Subscriber will be called in the same thread, which is posting the event. */
PostThread,
/** Subscriber will be called in Android's main thread (sometimes referred to as UI thread). */
MainThread,
/* BackgroundThread */
}
private String defaultMethodName = "onEvent";
public void register(Object subscriber) {
register(subscriber, defaultMethodName, ThreadMode.PostThread);
}
public void registerForMainThread(Object subscriber) {
register(subscriber, defaultMethodName, ThreadMode.MainThread);
}
public void register(Object subscriber, String methodName, ThreadMode threadMode) {
List<Method> subscriberMethods = findSubscriberMethods(subscriber.getClass(), methodName);
for (Method method : subscriberMethods) {
Class<?> eventType = method.getParameterTypes()[0];
subscribe(subscriber, method, eventType, threadMode);
}
}
这里可以看到register继续调用了对象内部的另外一个register方法,并传入了另外两个参数。
methodName这个大家根据名称应该就很容易能够猜出其功能,就是注册后,回调的方法的名称的名称,默认的方法名为"onEvent",也支持用户自己调用register方法传入自己想要的方法名。
ThreadMode这个参数是一个枚举值,EventBus一共提供了两个,一个是PostThread,就是保持消息发送方的Thread不变,类似于回调。另外一个就是MainThread,将调用方发过来的消息,传递到主线程调用,类似于Handler。
在register的方法中,我们看到了EventBus实现了一个消息列表List subscriberMethods 。并且还有一个新建Method的方法findSubscriberMethods。所以接下来我们要去看一下findSubscriberMethods的具体实现。
private List<Method> findSubscriberMethods(Class<?> subscriberClass, String methodName) {
String key = subscriberClass.getName() + '.' + methodName;
List<Method> subscriberMethods;
synchronized (methodCache) {
//从方法缓存中,取出方法列表
subscriberMethods = methodCache.get(key);
}
if (subscriberMethods != null) {
//如果取出的方法列表不为空,则直接将方法列表抛出
return subscriberMethods;
}
subscriberMethods = new ArrayList<Method>();
Class<?> clazz = subscriberClass;
HashSet<Class<?>> eventTypesFound = new HashSet<Class<?>>();
while (clazz != null) {
String name = clazz.getName();
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
// Skip system classes, this just degrades performance
break;
}
//获取class中所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
//获取方法的参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
if (eventTypesFound.add(parameterTypes[0])) {
//如果这个方法参数类型,在eventTypesFound没有发现,则将这个方法
//加入subscriberMethods
// Only add if not already found in a sub class
subscriberMethods.add(method);
}
}
}
}
//查找父类。父类中也有可能注册event事件
clazz = clazz.getSuperclass();
}
if (subscriberMethods.isEmpty()) {
throw new RuntimeException("Subscriber " + subscriberClass + " has no methods called " + methodName);
} else {
synchronized (methodCache) {
//将方法列表加入缓存
methodCache.put(key, subscriberMethods);
}
return subscriberMethods;
}
}
从上面的代码中,我们很简单的能看到其中的逻辑,就是通过反射获取对象所有方法,然后根据方法名找到其中注解时间,并将其中方法加入到方法列表中。这里要注意一个点,就是如果一个方法参数的类型已经在hashset中存在,就不会再被加入到方法列表中。并且这个查找方法是一个循环,会不断的向上查找class的父类。判断是否有绑定事件。
所以根据上面的代码,我们发现如果我们给Activity添加一个父类,然后代码写成下面这样的话,绑定的消息都是可以收到的。
public abstract class BaseActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public void onEvent(SecEventType event) {
Log.d(TAG, "onEvent: 我是父类我收到消息的线程"+Thread.currentThread().getName());
Log.d(TAG, "onEvent: 我是父类我收到消息"+event.getText());
}
}
public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
EventBus.getDefault().register(this, "OnEventTest", EventBus.ThreadMode.PostThread);
}
public void onEvent(AnyEventType event) {
Log.d(TAG, "onEvent: 我收到消息的线程" + Thread.currentThread().getName());
Log.d(TAG, "onEvent: 收到消息" + event.getText());
}
public void OnEventTest(String event) {
Log.d(TAG, "OnEventTest: 我收到消息的线程" + Thread.currentThread().getName());
Log.d(TAG, "OnEventTest: 收到消息" + event);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
public void click(View view) {
startActivity(new Intent(this, SecondActivity.class));
}
}
好了,findSubscriberMethods方法看完了,我们再回到register方法.
public void register(Object subscriber, String methodName, ThreadMode threadMode) {
List<Method> subscriberMethods = findSubscriberMethods(subscriber.getClass(), methodName);
for (Method method : subscriberMethods) {
Class<?> eventType = method.getParameterTypes()[0];
subscribe(subscriber, method, eventType, threadMode);
}
}
作用是取出方法列表然后遍历,再去调用subscribe方法。所以再次转移阵地
private void subscribe(Object subscriber, Method subscriberMethod, Class<?> eventType, ThreadMode threadMode) {
//根据参数类型从map取出CopyOnWriteArrayList
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
//如果没有找到对应的列表,则新建一个空列表,并存入map
subscriptions = new CopyOnWriteArrayList<Subscription>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//如果同一个对象,同一个方法名,则抛出异常。也就意味在同一个对象里面,不能写两个参数类型完全一样的订阅事件
//但是允许同一个对象拥有多个参数不同的订阅事件
//或者不同对象拥有同种参数类型的订阅事件
for (Subscription subscription : subscriptions) {
if (subscription.subscriber == subscriber) {
throw new RuntimeException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
}
//取消java的权限控制,这样可以访问private等的方法
subscriberMethod.setAccessible(true);
//将订阅相关内容封装成一个对象
Subscription subscription = new Subscription(subscriber, subscriberMethod, threadMode);
subscriptions.add(subscription);
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
//给每个订阅对象添加一个列表
subscribedEvents = new ArrayList<Class<?>>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//给订阅对象的订阅方法列表,添加一个订阅类型
subscribedEvents.add(eventType);
}
(这里要补充说明一下CopyOnWriteArrayList这个类,这个类和ArrayList一样,都实现了list接口。但是和ArrayList相比,增删都增加了锁,所以线程安全)
在这里我们总结一下,其实这个方法就干了两件事,涉及两个Map,一个map是以绑定对象为维度,保存订阅的方法。另外一个map是以绑定方法为维度,保存订阅的相关的对象,订阅方法,以及ThreadMode。
其实走到这里订阅相关的方法就已经走完了。那我们就来分析一下,如果按照我们之前写的方法subscriptionsByEventType这个map中应该有三个subscriptions。而typesBySubscriber中只有一个subscribedEvents,然后subscribedEvents中包含了三个方法。
我们将register方法改造一下,打印出我们这里两个hashmap
public void register(Object subscriber, String methodName, ThreadMode threadMode) {
List<Method> subscriberMethods = findSubscriberMethods(subscriber.getClass(), methodName);
for (Method method : subscriberMethods) {
Class<?> eventType = method.getParameterTypes()[0];
subscribe(subscriber, method, eventType, threadMode);
}
for (CopyOnWriteArrayList<Subscription> bean :
subscriptionsByEventType.values()) {
Log.d("EventBus","subscriptionsByEventType==");
for (int i=0;i<bean.size();i++){
Log.d("EventBus","=========================="+bean.get(i).subscriber+"."+bean.get(i).method);
}
}
for (List<Class<?>> bean :
typesBySubscriber.values()) {
Log.d("EventBus","typesBySubscriber==");
for (int i=0;i<bean.size();i++){
Log.d("EventBus","=========================="+bean.get(i).getName());
}
}
}
运行后我们获得了下面的结果
//第一次调用订阅
subscriptionsByEventType==
==========================com.lanlengran.eventbus10test.MainActivity.onEvent
subscriptionsByEventType==
==========================com.lanlengran.eventbus10test.MainActivity.onEvent
typesBySubscriber==
==========================com.lanlengran.eventbus10test.AnyEventType
==========================com.lanlengran.eventbus10test.SecEventType
//第二次调用订阅
subscriptionsByEventType==
==========================com.lanlengran.eventbus10test.MainActivity.onEvent
subscriptionsByEventType==
==========================com.lanlengran.eventbus10test.MainActivity.onEvent
subscriptionsByEventType==
==========================com.lanlengran.eventbus10test.MainActivity.OnEventTest
typesBySubscriber==
==========================com.lanlengran.eventbus10test.AnyEventType
==========================com.lanlengran.eventbus10test.SecEventType
==========================java.lang.String
好了,果然和我们预料的一样,第一次的时候,直接只绑定了MainActivity的方法和BaseActivity的方法,所以subscriptionsByEventType里包含两个对象,typesBySubscriber的里面只包含一个列表,但是列表里面有两个方法。
以此类推,在后面又调用注册的时候,方法变成了三个。
ok,订阅过程分析完毕。
发布消息
EventBus的发布消息也是极其非常简单的只需要
private void senMsg() {
Log.d("MainActivity", "run: 我运行在线程"+Thread.currentThread().getName());
EventBus.getDefault().post(new AnyEventType("这是第二个页面传递过来的"));
EventBus.getDefault().post(new SecEventType("这是第二个页面传递过来的"));
EventBus.getDefault().post("233333");
}
你看看,一行代码,一个消息,不要太简单。我三行代码三个消息。
所以,惯例,我们从post方法看起。
private final ThreadLocal<List<Object>> currentThreadEventQueue = new ThreadLocal<List<Object>>() {
@Override
protected List<Object> initialValue() {
return new ArrayList<Object>();
}
};
/**
* Posts the given event to the event bus.
*/
public void post(Object event) {
List<Object> eventQueue = currentThreadEventQueue.get();
eventQueue.add(event);
BooleanWrapper isPosting = currentThreadIsPosting.get();
//如果正在分发消息,则直接return
if (isPosting.value) {
return;
} else {
isPosting.value = true;
try {
while (!eventQueue.isEmpty()) {
//抛出列表的第一项,并从列表中移除
postSingleEvent(eventQueue.remove(0));
}
} finally {
isPosting.value = false;
}
}
}
这里第一句话,可能部分人不是很懂,就是这个currentThreadEventQueue,这个currentThreadEventQueue是一个ThreadLocal对象。那么这个ThreadLocal如果你看过我前面的handler的源码解析的文章,那么你就应该清楚他的作用是啥。在这里我在解释一下,ThreadLocal可以保证每个thread中的变量互补干扰,即使同名。
也就是说,每个线程中,都会有自己单独的List eventQueue。
这里除了ThreadLocal这个知识点以外,实在没有啥好讲的。其中会把消息传递到postSingleEvent方法。
private void postSingleEvent(Object event) throws Error {
//查找到event事件类的包含自己,以及所有的父类,并将其存入列表中
List<Class<?>> eventTypes = findEventTypes(event.getClass());
boolean subscriptionFound = false;
//event事件有多少个父类
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(clazz);
}
if (subscriptions != null) {
//一个event事件可能会被多个对象订阅,所以需要遍历
for (Subscription subscription : subscriptions) {
if (subscription.threadMode == ThreadMode.PostThread) {
postToSubscribtion(subscription, event);
} else if (subscription.threadMode == ThreadMode.MainThread) {
mainThreadPoster.enqueue(event, subscription);
} else {
throw new IllegalStateException("Unknown thread mode: " + subscription.threadMode);
}
}
subscriptionFound = true;
}
}
if (!subscriptionFound) {
Log.d(TAG, "No subscripers registered for event " + event.getClass());
}
}
这里的逻辑也很简单了,就是首先找出event事件的父类,然后再去我们订阅的时候用event事件为key的map中去找对应的订阅信息。最后再根据不同的ThreadMode调用不同的方法。
首先我们看下ThreadMode.PostThread的处理方法postToSubscribtion
static void postToSubscribtion(Subscription subscription, Object event) throws Error {
try {
subscription.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
+ subscription.subscriber.getClass(), cause);
if (cause instanceof Error) {
throw (Error) cause;
}
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
额, 这。。。。
这实际上就一句话,就是用反射执行了这个方法,所以我们还是去看看ThreadMode.MainThread的方法吧。
首先我们需要关注一下,这个mainThreadPoster是个什么玩意。
private PostViaHandler mainThreadPoster;
public EventBus() {
subscriptionsByEventType = new HashMap<Class<?>, CopyOnWriteArrayList<Subscription>>();
typesBySubscriber = new HashMap<Object, List<Class<?>>>();
mainThreadPoster = new PostViaHandler(Looper.getMainLooper());
}
final static class PostViaHandler extends Handler {
PostViaHandler(Looper looper) {
super(looper);
}
void enqueue(Object event, Subscription subscription) {
PendingPost pendingPost = PendingPost.obtainPendingPost(event, subscription);
Message message = obtainMessage();
message.obj = pendingPost;
if (!sendMessage(message)) {
throw new RuntimeException("Could not send handler message");
}
}
@Override
public void handleMessage(Message msg) {
PendingPost pendingPost = (PendingPost) msg.obj;
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
PendingPost.releasePendingPost(pendingPost);
postToSubscribtion(subscription, event);
}
}
好嘛,也没啥,就是封装了一个handler。而所谓的ThreadMode.MainThread中执行的方法,不过是通过handler将事件传递了出去,然后再handleMessage中调用了,我们前面看到的postToSubscribtion方法。执行了对应的方法。
取消订阅
eventbus的取消订阅的方法也非常简单,只需要调用下面的代码即可。
EventBus.getDefault().unregister(this);
所以我们来看一下unregister的相关逻辑
/**
* Unregisters the given subscriber from all event classes.
*/
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
unubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
上面代码的意思很明确,通过订阅对象查询到所有的订阅方法,然后依次调用unubscribeByEventType。
/**
* Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber.
*/
private void unubscribeByEventType(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
if (subscriptions.get(i).subscriber == subscriber) {
subscriptions.remove(i);
i--;
size--;
}
}
}
}
这里代码的逻辑也非常明确,就是从subscriptionsByEventType中移除该对象对应的订阅方法。
而unregister方法,在执行完unubscribeByEventType以后,也会通过 typesBySubscriber.remove(subscriber);移除在typesBySubscriber中的对象。
所以总结一下,取消订阅的方法就是移除subscriptionsByEventType和typesBySubscriber中的与订阅对象相关的内容。
总结
我们从上到下查看了eventbus从订阅到发布到取消订阅的相关代码。发现代码的核心就是在subscriptionsByEventType和typesBySubscriber这两个map上面。
subscriptionsByEventType是通过订阅者保存相关的订阅信息,用来取消订阅的时候查找到所有的订阅方法。
而subscriptionsByEventType则是通过订阅的参数类型即event类型来保存相关的订阅信息,用来在发布事件的时候查找到相关的订阅者以及订阅方法
拓展
在阅读了EventBus的源码以后,有种作者行,我也行的错觉。所以就山寨了一个EventBus,去除了一切的线程安全相关,以及速度优化相关,仅展示其基本原理。
以下是山寨EventBus代码
public class MyEventBus {
private static MyEventBus instance = new MyEventBus();
private static String defaultMthodName = "onEvent";
private HashMap<Object, List<Method>> methodListByRegister = new HashMap<>();
private HashMap<Object, List<Subscription>> methodListByEvent = new HashMap<>();
private List<Object> eventQueue = new ArrayList<>();
boolean isPost = false;
private static final String TAG = "MyEventBus";
public static MyEventBus getDefault() {
return instance;
}
public enum ThreadMode {
PostThread, MainThread
}
public void register(Object register) {
register(register, ThreadMode.PostThread, defaultMthodName);
}
public void registerForMainThread(Object register) {
register(register, ThreadMode.MainThread, defaultMthodName);
}
public void register(Object register, ThreadMode threadMode, String methodName) {
List<Method> methodList = findRegistserMethod(register, methodName);
for (Method method : methodList) {
subscribeMethod(register, method, threadMode);
}
}
private void subscribeMethod(Object register, Method method, ThreadMode threadMode) {
List<Subscription> subscriptionList = methodListByEvent.get(method.getParameterTypes()[0]);
if (subscriptionList == null) {
subscriptionList = new ArrayList<>();
methodListByEvent.put(method.getParameterTypes()[0], subscriptionList);
}
Subscription subscription = new Subscription(register, method, threadMode);
subscriptionList.add(subscription);
List<Method> methods = methodListByRegister.get(register);
if (methods == null) {
methods = new ArrayList<>();
methodListByRegister.put(register, methods);
}
methods.add(method);
}
private List<Method> findRegistserMethod(Object register, String methodName) {
Class registerClass = register.getClass();
List<Method> methodList = new ArrayList<>();
HashSet<Class> hashSet = new HashSet<>();
while (registerClass != null) {
if (registerClass.getName().startsWith("java.") || registerClass.getName().startsWith("android.")
|| registerClass.getName().startsWith("javax.")) {
break;
}
Method[] methods = registerClass.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] params = method.getParameterTypes();
if (params.length == 1) {
if (hashSet.add(params[0])) {
methodList.add(method);
}
}
}
}
registerClass = registerClass.getSuperclass();
}
return methodList;
}
public void post(Object event) {
eventQueue.add(event);
isPost = true;
while (eventQueue.size() > 0) {
Object sendEvent = eventQueue.remove(0);
if (sendEvent == null) {
continue;
}
List<Subscription> subscriptionList = methodListByEvent.get(event.getClass());
if (subscriptionList != null) {
for (Subscription subscription : subscriptionList) {
try {
if (subscription.threadMode == ThreadMode.PostThread) {
subscription.method.invoke(subscription.register, event);
} else if (subscription.threadMode == ThreadMode.MainThread) {
// 暂未实现
subscription.method.invoke(subscription.register, event);
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "post: " + e.getMessage());
}
}
}
}
isPost = false;
}
public void unregister(Object register) {
List<Method> methodList= methodListByRegister.get(register);
if (methodList==null){
return;
}
for (Method method:methodList){
List<Subscription> subscriptionList=methodListByEvent.get(method.getParameterTypes()[0]);
for (int i=0;i<subscriptionList.size();i++){
Subscription subscription=subscriptionList.get(i);
if (subscription.register==register){
subscriptionList.remove(subscription);
i--;
}
}
}
methodListByRegister.remove(register);
}
private static class Subscription {
public Object register;
public Method method;
public ThreadMode threadMode;
public Subscription(Object register, Method method, ThreadMode threadMode) {
this.register = register;
this.method = method;
this.threadMode = threadMode;
}
}
}
感想
至此,我们就看完了整个EventBus1.0的实现细节,以及相关的订阅和发布消息。可以看出来整个的逻辑还是非常的清晰的,代码非常的易懂,并且里面用到的内容也是我们经常见到的。
但是我现在看到这些代码,还是只能知其然,不能知其所以然。果然自己还是好菜啊