仿 EventBus 打造指向性通信框架

我们在使用 EventBus 的时候,要发送一个事件往往都需要创建类来标明事件,当事件多了起来,事件类的数量也就跟着膨胀了。那么我们何不打造一个类似的框架,通过标签来使事件有指向性的发出与接收,在发送事件的时候指定标签,那么只有声明了该标签的方法才会接受到此事件。当有大量的事件的时候,我们也只需要维护好标签表就行了,而不需要大量的创建类型。

使用范例

首先,可以在 Activity 或 Fragment 初始化的时候进行注册,KBus 是一个单例类,调用其register()方法将要接收事件的类对象传入其中。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 注册
    KBus.getDefault().register(this);
}

然后,定义一个方法,使用@Subscribe注解修饰,并传入一个字符串数组作为注解的值,表示一组标签。方法也可以定义参数来接收发送事件时传入的参数。

@Subscribe({"1","2"})
public void test1(String name) {
    Log.e(TAG, "test1:" + name);
}

注册与定义好方法后,我们可以在任何需要的地方进行事件的发送,调用 KBus 的post()方法,第一个参数为label,即标签,当发送事件后,定义了此标签的方法才会接收到该事件,第二个参数为一个可变参数,里面放的是需要传递给接收的方法的参数。

KBus.getDefault().post("label","param1","param2");

最后,需要在 Activity 或 Fragment 销毁的时候进行注销,同样传入注册时的对象。

KBus.getDefault().unregister(this);

原理解析

从上面的使用可以看到,我们需要使用 KBus 对象来进行类的注册、注销以及事件的发送,需要使用@Subscribe注解来修饰方法并给注解传入标签组,从而接收相应的事件。

首先,我们来定义一个自定义注解Subscribe

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    String[] value();
}
  • @Target(ElementType.METHOD)表示该注解修饰的是方法;
  • @Retention(RetentionPolicy.RUNTIME)表示该注解可以在运行时通过反射获取解析;
  • String[] value()表示传入一组标签,用来定义方法要接收那些事件;

自定义注解定义完后,我们来编写核心类KBus

由于 KBus 类会在很多地方都需要用到,所以,我们将它定义为单例类,避免对象的重复创建。

public class KBus {

    private static KBus sInstance = new KBus();

    private KBus() { }

    public static KBus getDefault() {
        return sInstance;
    }

    ...代码下面讲解

}

之后,我们需要在 KBus 类里定义三个缓存表。缓存表的作用是将我们第一次反射得到的各种数据缓存起来,再次使用的时候直接从缓存表里拿,从而减少多次反射带来的性能消耗。

// 方法缓存表
// key:class-当前注册的类的 Class 对象
// value:List<SubscribeMethod>-方法信息封装类集合
private Map<Class, List<SubscribeMethod>> methodCache = new HashMap<>();

// 方法执行表
// key:String-标签
// value:List<SubscribeMethodInvoke>-方法执行信息封装类集合
private Map<String, List<SubscribeMethodInvoke>> invokeCache = new HashMap<>();

// 方法注销表
// key:class-当前注册的类的 Class 对象
// value:List<String>-需要注销的标签集合
private Map<Class, List<String>> unregisterCache = new HashMap<>();

先来解释解释这三个表

方法缓存表:当调用 KBus 的register()方法进行注册的时候,会传入一个实例对象,然后通过反射拿到这个对象所有使用 @Subscribe 注解修饰的方法,然后解析这些方法将其封装到SubscribeMethod类。

SubscribeMethod

public class SubscribeMethod {
    // 标签
    private String label;
    // 方法反射类
    private Method method;
    // 参数类型组
    private Class[] paramClass;

    ...Getter/Setter
}

方法执行表:同样是在调用register()方法的时候,当拿到了使用 @Subscribe 注解修饰的所有方法后,需要根据方法定义的标签来对方法进行分组缓存,并将SubscribeMethod与当前注册的对象封装到SubscribeMethodInvoke类。

当调用post()方法发送事件时,我们就可以通过post()方法传入的标签来拿到相应的需要执行的方法来执行。

SubscribeMethodInvoke

public class SubscribeMethodInvoke {
    // 方法信息封装类
    private SubscribeMethod subscribeMethod;
    // 执行方法的对象
    private Object subscribeObj;

    ...Getter/Setter
}

方法注销表:当调用register()方法的时候,会构建方法执行表,同时会将每个注册对象对应的要执行的方法的标签缓存到方法注销表中。当调用unregister()方法的时候,就可以传入对象的 Class 类,然后拿出要注销的方法的标签集合遍历,通过标签拿到对应的要执行的所有的方法封装类,如果封装类里面的对象与要注销的对象相同,就将此方法封装类移出方法执行表。

分析完三个表的作用后,先来实现register()方法,这里,需要构建之前定义好的三个缓存表。

public void register(Object obj) {
    ...代码下面讲解
}

首先构建方法缓存表

// 获取注册对象的 Class
Class<?> objClass = obj.getClass();
// 在 findSubscribeMethodList() 里面构建方法缓存表
// 将当前注册对象中所有用 Subscribe 注解修饰的方法的封装对象集合返回
List<SubscribeMethod> subscribeMethodList = findSubscribeMethodList(objClass);
private List<SubscribeMethod> findSubscribeMethodList(Class<?> objClass) {
    // 首先从方法缓存表中获取
    List<SubscribeMethod> subscribeMethodList = methodCache.get(objClass);
    // 方法缓存表中有则直接返回缓存数据,没有则进行解析
    if (subscribeMethodList == null) {
        subscribeMethodList = new ArrayList<>();
        // 反射获取所有方法
        Method[] methods = objClass.getDeclaredMethods();
        // 遍历找出带有 Subscribe 注解的方法
        for (Method method : methods) {
            // 获取方法上的注解,只处理带有 Subscribe 注解的方法
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
            if (subscribe != null) {
                // 获取注解里的标签组
                String[] labelArray = subscribe.value();
                // 获取方法的参数类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (String label : labelArray) {
                    // 设置方法权限
                    method.setAccessible(true);
                    // 每个标签都对应一个 SubscribeMethod 对象
                    subscribeMethodList.add(new SubscribeMethod(
                            label, method, parameterTypes));
                }
            }
        }
        // 添加集合到方法缓存表
        methodCache.put(objClass, subscribeMethodList);
    }
    return subscribeMethodList;
}

接着初始化方法注销表

// 从注销缓存表中获取要注销的标签集合
// 如果没有则初始化并添加到缓存表中
List<String> unregisterList = unregisterCache.get(objClass);
if (unregisterList == null) {
    unregisterList = new ArrayList<>();
    unregisterCache.put(objClass, unregisterList);
}

最后来构建方法执行表,同时为方法注销表集合添加数据

// 遍历所有使用 Subscribe 注解修饰的方法集合
for (SubscribeMethod subscribeMethod : subscribeMethodList) {
    // 获取标签
    String label = subscribeMethod.getLabel();

    // 如果注销表中没有此标签则添加到注销表中
    if (!unregisterList.contains(label)) {
        unregisterList.add(label);
    }

    // 根据标签从方法执行表中获取集合
    List<SubscribeMethodInvoke> subscribeMethodInvokeList = invokeCache.get(label);
    // 集合为空则初始化并添加到方法执行表中
    if (subscribeMethodInvokeList == null) {
        subscribeMethodInvokeList = new ArrayList<>();
        invokeCache.put(label, subscribeMethodInvokeList);
    }
    // 将每个使用 Subscribe 注解修饰的方法封装成执行对象并添加到集合中
    subscribeMethodInvokeList.add(new SubscribeMethodInvoke(subscribeMethod, obj));
}

到这里 register() 方法就已经实现完成了,主要的工作就是解析注册对象的类通过反射来获取到相应的数据并缓存起来。

下面我们来实现 post() 方法,在这个方法里,我们需要根据传入的标签,拿到标签对应的方法集合,然后处理传入的参数,最后执行方法。

public void post(String label, Object... params) {
    // 通过 label 拿到需要执行的方法集合,为空则不往下执行
    List<SubscribeMethodInvoke> invokeList = invokeCache.get(label);
    if (invokeList == null || invokeList.size() == 0) {
        return;
    }

    for (SubscribeMethodInvoke methodInvoke : invokeList) {
        // 组装参数
        // 参数可传可不传,如不传则为null
        Class[] paramClass = methodInvoke.getSubscribeMethod().getParamClass();
        Object[] realParams = new Object[paramClass.length];
        if (params != null) {
            // 遍历参数类型列表
            for (int i = 0; i < paramClass.length; i++) {
                // 1. 当前下标小于传入参数的长度
                // 2. 当前下标的参数类型与传入的参数一致
                if (i < params.length && paramClass[i].isInstance(params[i])) {
                    realParams[i] = params[i];
                } else {
                    realParams[i] = null;
                }
            }
        }

        // 执行方法
        try {
            // 需要执行的方法
            Method invokeMethod = methodInvoke.getSubscribeMethod().getMethod();
            // 需要执行方法的对象
            Object subscribeObj = methodInvoke.getSubscribeObj();   
            // 执行方法
            invokeMethod.invoke(subscribeObj, realParams);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后来实现 unregister() 方法

public void unregister(Object obj) {
    // 从注销表中获取要注销的标签集合
    List<String> unregisterList = unregisterCache.get(obj.getClass());
    if (unregisterList == null || unregisterList.size() == 0) {
        return;
    }
    for (String label : unregisterList) {
        // 获取对应的标签要执行的所有方法遍历之
        List<SubscribeMethodInvoke> invokeList = invokeCache.get(label);
        if (invokeList != null && invokeList.size() > 0) {
            Iterator<SubscribeMethodInvoke> iterator = invokeList.iterator();
            while (iterator.hasNext()) {
                SubscribeMethodInvoke invoke = iterator.next();
                // 如果当前方法的执行对象和要注销的对象一样,就将方法移出执行表
                if (invoke.getSubscribeObj() == obj) {
                    iterator.remove();
                }
            }
        }
    }
}

例子

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "KBus-Test";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 注册
        KBus.getDefault().register(this);

        // 发送事件
        // 标签为"1",那么test1()与test2方法都会接收到事件
        KBus.getDefault().post("1","fancyluo","25");
    }

    @Subscribe({"1"})
    public void test1() {
        Log.e(TAG, "text1");
    }

    @Subscribe({"1","2"})
    public void test2(String name,String age) {
        Log.e(TAG, "text2:" + name + "-" + age);
    }

    @Subscribe({"3"})
    public void test3() {
        Log.e(TAG, "text3");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销
        KBus.getDefault().unregister(this);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值