摘要
本文介绍一种基于Java动态代理实现的总线通信组件,使用简单,可以实现双向通信。
背景
在Android开发中,经常需要在不同的组件之间通信。函数调用就可以看成是一种通信,调用者和被调用的函数是消息的发送方和接收方,参数和返回值是消息内容。除了直接调用以外,比较常见的就是总线形式的通信。
总线通信有很多种实现,例如EventBus、RxBus、LiveEventBus等,Android原生的Handler也可以作为总线通信组件使用。常用的Observer设计模式也可以理解成专用的总线通信,只能负责收发固定类型的事件。
多数通用总线通信组件都是单向通信,即发送方主动将Message/Event发送给接收方,但有时候会有不同的需求,发送方希望主动获取接收方的数据,此时需要接收方在受到拉取数据的Message之后再回发一个Message。
实现
下面介绍一些基于动态代理实现的总线通信组件,发送方可以直接调用接收方的方法,由于方法可以有参数和返回值,因此可以实现双向通信。
总线通信在代码层面的本质就是一个全局变量,最简单的做法是直接有一个全局的Set保存所有Handler(Handler为接收方),调用方从Set中遍历符合条件的Handler然后逐个调用即可。但是这样的做法调用起来比较繁琐,于是在此基础上借助Java的动态代理做了封装,用起来更方便,效果有点类似jQuery中批量操作DOM元素的写法。关键代码如下。其中Set<Object> mHandlers
中保存所有的Handler,通过register和unregister方法注册/解注册Handler。
public class EventBus {
private final Set<Object> mHandlers = new HashSet<>();
public static EventBus getDefault() {
return Holder.BUS;
}
/**
* 添加Handler
*/
public void register(Object handler) {
if (handler != null) {
mHandlers.add(handler);
}
}
/**
* 删除Handler
*/
public void unregister(Object handler) {
if (handler != null) {
mHandlers.remove(handler);
}
}
/**
* 根据接口调用所有匹配的Handler
*
* @param clazz 接口
* @return 返回一个代理对象,调用其方法相当于调用所有实例的对应方法,返回第一个实例的方法调用返回值
*/
@NonNull
@SuppressWarnings("unchecked")
public <Handler> Handler find(Class<Handler> clazz) {
final Class<?>[] interfaces = new Class[]{clazz};
final EventInvocationHandler<Handler> handler = new EventInvocationHandler<>(clazz);
return (Handler) Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, handler);
}
/**
* 根据接口获取所有匹配的Handler
*
* @param clazz 接口
* @return 返回一个 {@link IterableList}
*/
@NonNull
@SuppressWarnings("unchecked")
public <Handler> IterableList<Handler> findAll(Class<Handler> clazz) {
IterableList<Handler> list = new IterableList<>();
for (Object handler : mHandlers) {
if (clazz.isInstance(handler)) {
list.add((Handler) handler);
}
}
return list;
}
private static class Holder {
private static final EventBus BUS = new EventBus();
}
private class EventInvocationHandler<Handler> implements InvocationHandler {
private final Class<Handler> mClazz;
EventInvocationHandler(Class<Handler> clazz) {
mClazz = clazz;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
boolean first = true;
for (Object handler : mHandlers) {
if (mClazz.isInstance(handler)) {
if (first) {
result = method.invoke(handler, args);
first = false;
} else {
method.invoke(handler, args);
}
}
}
return result;
}
}
}
用法
1、定义接口
收发方按照一个定义好的Java接口通信。
public interface TestHandler {
String getName();
}
2、接收方的实现
作为接收方的Handler实现这个接口,并注册到EventBus中。
TestHandler handler = new TestHandler() {
@Override
public String getName() {
return "0";
}
}
EventBus.getDefault().register(handler);
3、基本调用方式
当调用方需要调用所有TestHandler时,可以调用EventBus的find方法,该方法返回一个TestHandler类型的动态代理对象,调用这个代理对象的getName方法,则所有TestHandler的相应方法就会被调用。返回值为第一个TestHandler对应方法的返回值。
这个调用过程不需要对find的返回值做判空,也不需要自行遍历每个TestHandler。
EventBus.getDefault().find(TestHandler.class).getName();
4、遍历调用方式
对于需要对匹配的元素逐个处理的情况,还提供了findAll方法,该方法返回一个IterableList(继承自ArrayList),可以自行逐个遍历元素,也可以调用each和map进行快捷操作。
bus.findAll(TestHandler.class).each(new EventAction<TestHandler>() {
@Override
public void run(TestHandler testHandler) {
testHandler.getName();
}
});
List<String> list = bus.findAll(TestHandler.class).map(new EventMapAction<TestHandler, String>() {
@Override
public String run(TestHandler handler) {
return handler.getName();
}
});
当然这种总线通信机制也有其不足,例如通信过程对于发送方是阻塞的,不能支持多线程之间的通信等。
完整的代码和测试用例详见GitHub。
最后,欢迎扫码关注微信公众号。程序员同行学习交流,聊天交友,国内外名企求职内推(微软 / 小冰 / Amazon / Shopee / Coupang / ATM / 头条 / 拼多多等),可加我微信 jzj2015 进技术群(备注进技术群,并简单自我介绍)。

本文由jzj1993原创,转载请注明来源:https://www.paincker.com/dynamic-proxy-java-bus
(标注了原文链接的文章除外)
暂无评论