EventBus是一个常用的消息框架,可以方便用于的跨组件之间的消息传递,比起官方提供的Handler,有着不需要考虑内存泄露、不需要写处理消息事件的回调、支持粘性事件、线程调度高效灵活等优点。
EventBus的使用非常简单,首先需要定义一个消息类(Event),只需要在发送消息的组件发送消息(post),在接受消息的组件(订阅者)定义一个入参为此消息类的公共方法,并添加订阅注解(Subscribe),在发送的消息传递到订阅者组件时就会调用此方法,有点类似于观察者模式。订阅者和消息的发送者需要保证在消息的传输过程中处于生命周期的存活阶段,如果发送消息时订阅者尚未被初始化,需要将消息设置为粘性事件(sticky),这样的消息在组件初始化后即可接收并处理,可以避免消息遗漏。消息的发送者需要在创建阶段执行注册(register),在销毁阶段执行注销(unregister)。EventBus也有缺点,比如频繁使用会创建大量的事件类。相关教程比较多,这里不再赘述。
这里仿照EventBus的思想,写一个简易的消息传递工具类。
创建一个订阅注解,持久度为运行时,注解对象为方法。
package tinybus.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
String goal() default ""; //目的地
}
因为是简化版本的EventBus,暂时叫TinyBus吧(因为体积非常小),支持空消息类发送,所以逻辑都精简在一个类中,主打一个简单易用。
package tinybus;
import tinybus.annotation.Subscribe;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
public class TinyBus {
//单例类
private static TinyBus instance;
/**获取单例 **/
public static TinyBus getInstance(){
if(instance == null){
synchronized (TinyBus.class){
instance = new TinyBus();
}
}
return instance;
}
//消息发送方
private Set<Object> hosts = new HashSet<>();
//发送方注册
public void register(Object object){
hosts.add(object);
}
//发送空消息
public void post(String goal){
post(null,goal);
}
//发送无目的地消息(自动寻找目的地)
public void post(Object message){
post(message,"");
}
/**
* 发送一条信息
* @param message 传输的数据类
* @param goal 目标方法注明
*/
public void post(Object message,String goal){
for(Object host:hosts){
for(Method method:host.getClass().getDeclaredMethods()){
method.setAccessible(true);
//入参校验,判断此方法的入参和message的类型一致
if(message == null ||( method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(message.getClass()))){
for(Annotation annotation:method.getDeclaredAnnotations()){
if(annotation instanceof Subscribe){
//执行方法
if(((Subscribe) annotation).goal().equals(goal)){
try {
if(message != null){
method.invoke(host,message);
}else{
method.invoke(host);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
}
}
}
//移除发送方
public void unRegister(Object object){
hosts.remove(object);
}
}
使用方式非常简单,首先创建一个事件的订阅类和消息类,为了方便测试这里将订阅者设计为单例模式。
消息类User
package bean;
public class User {
public String name;
public int age;
public String gender;
public User(){}
public User(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "bean.User{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
订阅类Subscriber
package starter;
import bean.User;
import tinybus.TinyBus;
import tinybus.annotation.Subscribe;
public class Subscriber {
private static Subscriber instance;
//获取单例
public static Subscriber getInstance(){
if(instance == null){
synchronized (Subscriber.class){
instance = new Subscriber();
}
}
return instance;
}
//注册TinyBus
protected Subscriber(){
TinyBus.getInstance().register(this);
}
//启动
public void start(){
System.out.println("start");
}
//测试方法,只有goal
@Subscribe(goal = "foo1")
public void foo(){
System.out.println("Hello!");
}
//测试方法,有goal也有消息类
@Subscribe(goal = "foo2")
public void foo(User user){
System.out.println("Hello!"+user.toString());
}
//测试方法,只有消息类
@Subscribe()
public void foo2(User user){
System.out.println("Hello!"+user.toString());
}
//测试方法,只有goal,测试注销后是否仍会执行订阅方法
@Subscribe(goal = "foo3")
public void foo3(){
System.out.println("I am offline!");
}
//退出,取消注册
public void exit(){
System.out.println("Bye!");
TinyBus.getInstance().unRegister(this);
}
}
写一个测试类,测试功能是否有效。
package starter;
import bean.User;
import tinybus.TinyBus;
public class TestBus {
public static void main(String[] args) {
Subscriber.getInstance().start(); //初始化subscriber
//让目的地为foo1的订阅方法执行
TinyBus.getInstance().post("foo1");
//让消息类为User的订阅方法执行
TinyBus.getInstance().post(new User("tom",18,"M"));
//让消息类为User且目的地为foo2的订阅方法执行
TinyBus.getInstance().post(new User("steven",24,"F"),"foo2");
//注销单例对象
Subscriber.getInstance().exit();
//测试单例对象注销后是否仍然执行订阅方法
TinyBus.getInstance().post("foo3");
}
}
运行效果
如图所示,消息订阅的单例对象成功执行了订阅方法,并且注销后不会继续订阅。
目前此版本的TinyBus功能比较简陋,后续可能会根据需求增加更多功能,比如支持类似于EventBus的ThreadMode,增强其健壮性和灵活性。