您的浏览器不支持CSS3,建议使用Firfox、Chrome等浏览器,以取得最佳显示效果

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

开发技术 580℃ 0 6个月前 (02-08)

摘要

本文介绍一种基于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。

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

最后,欢迎扫码关注微信公众号。微软 / Shopee / Coupang / BAT等国内外企业内推、行业和技术交流,也可以加我微信 jzj2015(注明来自博客),拉你进技术群。

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

0

暂无评论

评论前:需填写以下信息,或 登录

用户登录

忘记密码?