一、使用EventBus
在Android开发中,@Subscribe
注解通常用于事件总线(Event Bus)库中,例如GreenRobot的EventBus或Square的Otto。事件总线是一种发布-订阅模式的实现,它允许各个组件之间进行松耦合的通信。
使用@Subscribe
注解的方法可以订阅某个类型的事件,当该事件发布时,订阅了该事件的方法会被自动调用。下面是一个使用EventBus的示例:
1.添加依赖
在build.gradle
文件中添加EventBus的依赖:
implementation 'org.greenrobot:eventbus:3.2.0'
2.定义事件类
创建一个事件类,用于传递数据。
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
3.订阅事件
在需要接收事件的类中,使用@Subscribe
注解订阅事件。
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册EventBus
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销EventBus
EventBus.getDefault().unregister(this);
}
// 订阅事件
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
// 处理事件
Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
}
}
4.发布事件
在需要发布事件的地方,使用EventBus发布事件。
EventBus.getDefault().post(new MessageEvent("Hello EventBus!"));
在上面的示例中:
@Subscribe
注解的方法onMessageEvent
会在MessageEvent
事件发布时被调用。threadMode = ThreadMode.MAIN
表示该方法将在主线程中执行,其他选项包括BACKGROUND
、ASYNC
等。
这种模式的优点是可以实现组件之间的松耦合通信,不需要显式地传递引用或实现接口,代码更加简洁和易于维护。
二、@SubScribe注解解析
请先了解什么是自定义注解,点击查看
@Subscribe
注解的实现主要依赖于事件总线库的底层机制,这里以GreenRobot的EventBus为例,简要介绍其实现原理。
1. 注解定义
首先,@Subscribe
注解本身的定义非常简单,它只是一个标记,用于标识订阅事件的方法。以下是一个简化的示例:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
}
2. 注册订阅者
当你在代码中调用 EventBus.getDefault().register(this)
时,EventBus会扫描注册对象的所有方法,寻找带有@Subscribe
注解的方法。
public void register(Object subscriber) {
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
3. 查找订阅方法
subscriberMethodFinder.findSubscriberMethods
方法会通过反射机制,找到所有带有@Subscribe
注解的方法,并将这些方法信息封装到SubscriberMethod
对象中。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 通过反射获取所有方法
Method[] methods = subscriberClass.getDeclaredMethods();
List<SubscriberMethod> subscriberMethods = new ArrayList<>();
for (Method method : methods) {
// 检查是否带有@Subscribe注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
// 获取方法参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new EventBusException("Subscriber " + method.getDeclaringClass() + "." + method.getName() +
" must have exactly 1 parameter but has " + parameterTypes.length);
}
// 封装成SubscriberMethod对象
ThreadMode threadMode = subscribeAnnotation.threadMode();
SubscriberMethod subscriberMethod = new SubscriberMethod(method, threadMode, parameterTypes[0]);
subscriberMethods.add(subscriberMethod);
}
}
return subscriberMethods;
}
4. 保存订阅者信息
在找到所有订阅方法后,EventBus会将这些方法的信息保存起来,以便在事件发布时能够快速找到对应的订阅者。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
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);
}
}
subscriptions.add(newSubscription);
}
5. 发布事件
当你调用 EventBus.getDefault().post(event)
时,EventBus会根据事件类型找到所有订阅了该事件的方法,并调用这些方法。
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");
}
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
for (Class<?> clazz = eventClass; clazz != null; clazz = clazz.getSuperclass()) {
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
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));
}
}
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
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;
}
6. 调用订阅方法
postToSubscription
方法会根据 ThreadMode
确定在主线程还是后台线程调用订阅方法。
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);
}
}
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
总结
通过上述步骤,EventBus实现了从注册订阅者、查找订阅方法、保存订阅信息到发布事件和调用订阅方法的完整过程。@Subscribe
注解的作用就是标识哪些方法是订阅方法,而事件总线库则负责处理这些方法的注册和调用。