子曰:温故而知新,可以为师矣。 《论语》-- 孔子
Intent
意图: 跳转 + 传参 (局限性非常大)。Handler
:通常用来更新主线程 UI,使用不当容易出现内存泄漏。Interface
接口:仅限于同一线程中数据交互。BroadCastReceiver
:有序广播 + 无序广播。 onReceive() 方法不能超过 10 秒。AIDL
跨进程通信:代码阅读性不友好,维护成本偏高。publish / subscribe
消息事件总线。简化了应用程序内各个组件间,组件与后台线程间的通信。Handler
或者 BroadCastReceiver
通知更新主线程 UI
;多个 Fragment
之间需要通过 Listener
(监听)通信。这些需求都可以通过 EventBus
完成和实现。正确
使用// app 目录下的 build.gradle 文件 defaultConfig 节点下加入
//给注解处理器传参
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : 'com.kww.eventbusdemo.MyEventBusIndex' ]
}
}
// dependencies 节点下
implementation 'org.greenrobot:eventbus:3.2.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.2.0'
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
}
}
public class SendMessageEvent {
private String message;
public SendMessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
// MainActivity
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);
findViewById(R.id.btn_jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if(EventBus.getDefault().isRegistered(this)){
EventBus.getDefault().unregister(this);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void receiveMessage(SendMessageEvent event){
Log.e(TAG,event.getMessage());
}
}
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.btn_send_message).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new SendMessageEvent("Hello"));
}
});
}
}
com.kww.eventbusdemo E/MainActivity >>>>: Hello
POSTING
(默认):如果使用事件处理函数指定了线程模型为 POSTING
,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说 发布事件和接收事件在同一个线程
。在线程模型为 POSTING
的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起 ANR
。MAIN
:事件的处理会在 UI
线程中执行。事件处理时间不能太长,否则可能会导致ANR
。BACKGROUND
:如果事件是在 UI
线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行 UI
更新操作。ASYNC
:无论事件在哪个线程发布,该事件处理函数都会在新建的 子线程
中执行,同样,此事件处理函数中禁止进行 UI
更新操作。// MainActivity 类
findViewById(R.id.btn_jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送粘性事件
EventBus.getDefault().postSticky(new SendMessageEvent("粘性事件"));
startActivity(new Intent(MainActivity.this,SecondActivity.class));
}
});
public class SecondActivity extends AppCompatActivity {
private static final String TAG = "SecondActivity >>>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
EventBus.getDefault().register(this);
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void receiveMessageStick(SendMessageEvent event){
Log.e(TAG,event.getMessage());
}
@Override
protected void onDestroy() {
super.onDestroy();
if(EventBus.getDefault().isRegistered(this)){
EventBus.getDefault().unregister(this);
}
}
}
这就类似于列表与详情页的关系,我们的 MainActivity
就是列表页,我们发送了一个粘性事件,相当于我们点击某一个 item
,将当前 item
的 id
传递给 详情页去请求接口,那么我们在 SecondActivity
,也就是 详情页,先收到粘性事件的 id
,然后跳转到详情页面上。
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# And if you use AsyncExecutor:
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
以上就是 EventBus 3.0
之后的正确使用,可能有的人和我用的不太一样,其实上面这中写法才是正确的写法,具体的可以看官方文档:EventBus 注解处理器使用 以及GitHub传送门。
1. EventBus.getDefault().register(this);
在这行代码中, EventBus.getDefault()
方法获取的是 EventBus
对象,很明显这是一个单例模式,采用了双重检查模式 (DCL):
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}
我们看到 register()
方法的源码:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 用 subscriberMethodFinder 提供的方法,找到在 subscriber 这个类里面订阅的内容。
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
在这个方法中,它首先要拿到 List
这个集合,也就是传进来的订阅者所有的订阅的方法,接下来遍历订阅者的订阅方法来完成订阅者的订阅操作。对于SubscriberMethod
(订阅方法)类中,主要就是用保存订阅方法的 Method
对象、线程模式、事件类型、优先级、是否是粘性事件等属性。
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
//......
}
好的,我们进入这个 findSubscriberMethods()
方法,看一下是如何拿到订阅者所有方法的:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//从缓存中获取SubscriberMethod集合
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex
if (ignoreGeneratedIndex) {
//通过反射获取subscriberMethods
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
//在获得subscriberMethods以后,如果订阅者中不存在@Subscribe注解并且为public的订阅方法,则会抛出异常。
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;
}
}
首先从缓存中查找,如果找到了就立马返回。如果缓存中没有的话,则根据ignoreGeneratedIndex
选择如何查找订阅方法,该属性默认就是false
,它可以通过内部构建者类 EventBusBuilder
设置,表示是否忽略注解器生成的 MyEventBusIndex
,这个就是采用我上面所写的使用方式后,在编译期 EventBus
就会自动帮我生成 MyEventBusIndex
文件。最后,找到订阅方法后,放入缓存,以免下次继续查找。
至于 findUsingReflection()
方法 和 findUsingReflection()
方法就不细说了,就是通过反射来获取订阅者中所有的方法。并依据方法的类型,参数和注解来找到根据 EventBus
指定的规则所写的订阅方法。
在查找完所有的订阅方法以后便开始对所有的订阅方法进行注册:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//根据订阅者和订阅方法构造一个订阅事件
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//获取当前订阅事件中Subscription的List集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//该事件对应的Subscription的List集合不存在,则重新创建并保存在subscriptionsByEventType中
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//订阅者已经注册则抛出EventBusException
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//遍历订阅事件,找到比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;
}
}
//通过订阅者获取该订阅者所订阅事件的集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//将当前的订阅事件添加到subscribedEvents中
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
if (eventInheritance) {
//粘性事件的处理
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);
}
}
}
这段源码做了两件事,其一就是将 订阅方法
和 订阅者
封装到subscriptionsByEventType
和 typesBySubscriber
,其二就是如果是粘性事件的话,就立马投递、执行。
subscriptionsByEventType
:当我们 post 时,根据 EventType
找到订阅事件,从而去分发事件,处理事件的。typesBySubscriber
:调用 unregister(this)
的时候,根据订阅者找到 EventType
,又根据 EventType
找到订阅事件,从而对订阅者进行解绑。2. EventBus.getDefault().post(new SendMessageEvent("Hello"));
现在来说一下 post
发送。
/** Posts the given event to the event bus. */
public void post(Object event) {
//PostingThreadState保存着事件队列和线程状态信息
PostingThreadState postingState = currentPostingThreadState.get();
//获取事件队列,并将当前事插入到事件队列中
List<Object> 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()) {
//处理队列中的所有事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
首先从 PostingThreadState
对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由 postSingleEvent
方法进行处理,并移除该事件。来看看 postSingleEvent()
方法里做了什么:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
//eventInheritance表示是否向上查找事件的父类,默认为true
if (eventInheritance) {
//通过lookupAllEventTypes找到所有的父类事件并存在List中,然后通过postSingleEventForEventType方法对事件逐一处理
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);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
接下来看看 postSingleEventForEventType
方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
//取出该事件对应的Subscription集合
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//将该事件的event和对应的Subscription中的信息(包扩订阅者类和订阅方法)传递给postingState
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;
}
接下来看看 postToSubscription()
方法:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
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);
}
}
取出订阅方法的线程模式,之后根据线程模式来分别处理。举个例子,如果线程模式是 MAIN
,提交事件的线程是主线程的话则通过反射,直接运行订阅的方法,如果不是主线程,我们需要 mainThreadPoster
将我们的订阅事件入队列,mainThreadPoster
是HandlerPoster
类型的继承自Handler
,通过Handler
将订阅方法切换到主线程执行。
3. EventBus.getDefault().unregister(this);
我们最后说一下解绑过程
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
typesBySubscriber
就是在订阅者注册的过程中讲到过这个属性,它根据订阅者找到 EventType
,然后根据 EventType
和订阅者来得到订阅事件来对订阅者进行解绑。
// Subscribe 类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
}
/**
* 线程状态
*/
public enum ThreadMode {
// 事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程
POSTING,
// 事件的处理会在UI线程中执行,事件处理不应太长时间
MAIN,
// 后台进程,处理如保存到数据库等操作
BACKGROUND,
// 异步执行,另起线程操作。事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作
ASYNC
}
/**
* 保存符合要求的订阅方法封装类
*/
public class MethodManager {
// 订阅者的回调方法(注解方法)的参数类型
private Class<?> type;
// 订阅者的回调方法(注解方法)的线程模式
private ThreadMode threadMode;
// 订阅者的回调方法(注解方法)
private Method method;
public MethodManager(Class<?> type, ThreadMode threadMode, Method method) {
this.type = type;
this.threadMode = threadMode;
this.method = method;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
public ThreadMode getThreadMode() {
return threadMode;
}
public void setThreadMode(ThreadMode threadMode) {
this.threadMode = threadMode;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@NonNull
@Override
public String toString() {
return "MethodManager{" +
"type=" + type +
", threadMode=" + threadMode +
", method=" + method +
'}';
}
}
public class EventBus {
// volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存
private static volatile EventBus instance;
// 用来保存这些带注解的方法(订阅者的回调方法)
private Map<Object, List<MethodManager>> cacheMap;
private Handler handler;
private ExecutorService executorService;
private EventBus() {
cacheMap = new HashMap<>();
// Handler高级用法:将handler放在主线程使用
handler = new Handler(Looper.getMainLooper());
// 创建一个子线程(缓存线程池)
executorService = Executors.newCachedThreadPool();
}
public static EventBus getDefault() {
if (instance == null) {
synchronized (EventBus.class) {
if (instance == null) {
instance = new EventBus();
}
}
}
return instance;
}
// 找到MainActivity所有带符合注解的方法
public void register(Object getter) {
// 获取MainActivity所有的方法
List<MethodManager> methodList = cacheMap.get(getter);
if (methodList == null) { // 不为空表示以前注册完成
methodList = findAnnotationMethod(getter);
cacheMap.put(getter, methodList);
}
}
// 获取MainActivity中所有注解的方法
private List<MethodManager> findAnnotationMethod(Object getter) {
List<MethodManager> methodList = new ArrayList<>();
// 获取类
Class<?> clazz = getter.getClass();
// 获取所有方法
Method[] methods = clazz.getMethods();
// 性能优化。N个父类不可能有自定义注解。排除后再反射
while (clazz != null) {
// 找出系统类,直接跳出,不添加cacheMap(因为不是订阅者)
String clazzName = clazz.getName();
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.")
|| clazzName.startsWith("android.")) {
break;
}
// 循环方法
for (Method method : methods) {
// 获取方法的注解
Subscribe subscribe = method.getAnnotation(Subscribe.class);
// 判断注解不为空,切记不能抛异常
if (subscribe == null) {
continue;
}
// 严格控制方法格式和规范
// 方法必须是返回void(一次匹配)
Type returnType = method.getGenericReturnType();
if (!"void".equals(returnType.toString())) {
throw new RuntimeException(method.getName() + "方法返回必须是void");
}
// 方法参数必须有值(二次匹配)
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new RuntimeException(method.getName() + "方法有且只有一个参数");
}
// 完全符合要求、规范的方法,保存到方法对象中MethodManager(3个重要成员:方法、参数、线程)
MethodManager manager = new MethodManager(parameterTypes[0], subscribe.threadMode(), method);
methodList.add(manager);
}
// 不断循环找出父类含有订阅者(注解方法)的类。直到为空,比如AppCompatActivity没有吧
clazz = clazz.getSuperclass();
}
return methodList;
}
// SecondActivity发送消息
public void post(final Object setter) {
// 订阅者已经登记,从登记表中找出
Set<Object> set = cacheMap.keySet();
// 比如获取MainActivity对象
for (final Object getter : set) {
// 获取MainActivity中所有注解的方法
List<MethodManager> methodList = cacheMap.get(getter);
if (methodList != null) {
// 循环每个方法
for (final MethodManager method : methodList) {
// 有可能多个方法的参数一样,从而都同时收到发送的消息
// 通过EventBean来判断是否匹配上
if (method.getType().isAssignableFrom(setter.getClass())) {
// 通过方法的类型匹配,从SecondActivity发送的EventBean对象(参数)
// 匹配MainActivity中所有注解的方法符合要求的,都发送消息
// class1.isAssignableFrom(class2) 判定此 Class 对象所表示的类或接口
// 与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
// 线程调度
switch (method.getThreadMode()) {
case POSTING:
invoke(method, getter, setter);
break;
case MAIN:
// 先判断发送方是否在主线程
if (Looper.myLooper() == Looper.getMainLooper()) {
invoke(method, getter, setter);
} else { // 子线程 - 主线程,切换线程(用到Handler)
handler.post(new Runnable() {
@Override
public void run() {
invoke(method, getter, setter);
}
});
}
break;
case BACKGROUND:
// 先判断发送方是否在主线程
if (Looper.myLooper() == Looper.getMainLooper()) {
// 主线程 - 子线程,创建一个子线程(缓存线程池)
executorService.execute(new Runnable() {
@Override
public void run() {
invoke(method, getter, setter);
}
});
} else { // 子线程 到 子线程,不用切换线程
invoke(method, getter, setter);
}
break;
}
}
}
}
}
}
// 找到匹配方法后,通过反射调用MainActivity中所有符合要求的方法
private void invoke(MethodManager method, Object getter, Object setter) {
Method execute = method.getMethod();
try {
execute.invoke(getter, setter);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游
至此,EventBus
的正确使用
、原理理解
以及 手写实现
就说到这,各位看官食用愉快。