一种基于动态代理实现的Android/Java总线通信组件

背景

在Android开发中,经常需要在不同的组件之间通信。函数调用就可以看成是一种通信,调用者和被调用的函数是消息的发送方和接收方,参数和返回值是消息内容。除了直接调用以外,比较常见的就是总线形式的通信。

总线通信有很多种实现,例如EventBusRxBusLiveEventBus等,Android原生的Handler也可以作为总线通信组件使用。常用的Observer设计模式也可以理解成专用的总线通信,只能负责收发固定类型的事件。

多数通用总线通信组件都是单向通信,即发送方主动将Message/Event发送给接收方,但有时候会有不同的需求,发送方希望主动获取接收方的数据,此时需要接收方在受到拉取数据的Message之后再回发一个Message。

实现

下面介绍一些基于动态代理实现的总线通信组件,发送方可以直接调用接收方的方法,由于方法可以有参数和返回值,因此可以实现双向通信。

总线通信在代码层面的本质就是一个全局变量,最简单的做法是直接有一个全局的Set保存所有Handler(Handler为接收方),调用方从Set中遍历符合条件的Handler然后逐个调用即可。但是这样的做法调用起来比较繁琐,于是在此基础上借助Java的动态代理做了封装,用起来更方便,效果有点类似jQuery中批量操作DOM元素的写法。关键代码如下。其中Set<Object> mHandlers中保存所有的Handler,通过register和unregister方法注册/解注册Handler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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接口通信。

1
2
3
public interface TestHandler {
String getName();
}

2、接收方的实现

作为接收方的Handler实现这个接口,并注册到EventBus中。

1
2
3
4
5
6
7
8
TestHandler handler = new TestHandler() {
@Override
public String getName() {
return "0";
}
}

EventBus.getDefault().register(handler);

3、基本调用方式

当调用方需要调用所有TestHandler时,可以调用EventBus的find方法,该方法返回一个TestHandler类型的动态代理对象,调用这个代理对象的getName方法,则所有TestHandler的相应方法就会被调用。返回值为第一个TestHandler对应方法的返回值。

这个调用过程不需要对find的返回值做判空,也不需要自行遍历每个TestHandler。

1
EventBus.getDefault().find(TestHandler.class).getName();

4、遍历调用方式

对于需要对匹配的元素逐个处理的情况,还提供了findAll方法,该方法返回一个IterableList(继承自ArrayList),可以自行逐个遍历元素,也可以调用each和map进行快捷操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
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