EventBus框架总结之支持泛型参数

来源:互联网 发布:淘宝卖家客服转人工 编辑:程序博客网 时间:2024/05/21 05:18

EventBus框架总结之用法
EventBus框架总结之源码分析


    前面两篇对EventBus的使用以及实现源码进行了总结,这一篇主要对EventBus源码的修改实现支持泛型参数的总结。

EventBus也会有烦恼


    在EventBus框架总结之用法中介绍时提到,当系统登录之后通过EventBus发送一个LoginEvent;在用户注册成功的时候,发送一个RegisterEvent;那用户退出登录时,同样需要发送一个LogoutEvent事件;当应用规模发展比较大的时候,每次需要通过EventBus发送一个新的事件时,都需要新定义一个类,造成Event类的泛滥,可能就是下面这个样子,

public class LoginEvent{}public class LogoutEvent{}public class RegisterEvent{}...可能中间有几十上百个类...public class AppExitEvent{}

    对于大部分有代码洁癖的人来说,遇到这样的情况都是不能被接受的。这种情况下大家第一个会想到Java泛型,若要了解泛型原理,请查看Java泛型原理详解。

Java泛型真能实现吗?


    我可以先告诉你,答案是不能够实现,不然也没有这篇文章的必要了。我们先来看看我们通常使用的情形,代码走起

public class LoginInfo{}public class RegisterInfo{} @Override public void onClick(View v) {        int id  = v.getId();        switch (id) {            case R.id.login_success_text_view:                loginSuccess();                break;            case R.id.register_success_text_view:                registerSuccess();                break;            default:                break;        }    }    private void loginSuccess() {        LoginEvent event = new LoginEvent();        event.info = new LoginInfo();        EventBus.getDefault().post(event);    }    private void registerSuccess() {        RegisterEvent event = new RegisterEvent();        event.info = new RegisterInfo();        EventBus.getDefault().post(event);    }    @SuppressWarnings({"unused","登陆成功之后回调"})    @Subscribe(threadMode = ThreadMode.MAIN)    public void onLoginSuccess(LoginEvent event) {        if(event.info != null) {            Toast.makeText(this, event.info.getInfo(), Toast.LENGTH_SHORT).show();        }    }    @SuppressWarnings({"unused","注册成功之后回调"})    @Subscribe(threadMode = ThreadMode.MAIN)    public void onRegisterSuccess(RegisterEvent event) {        if(event.info != null) {            Toast.makeText(this, event.info.getInfo(), Toast.LENGTH_SHORT).show();        }    }

    PS:SuppressWarnings这个注解是我的强迫症才加入的,跟EventBus无关,因为没有直接引用的方法,在IDE里面方法名称会变成灰色;通过SuppressWarnings这个注解既可以让方法名称不变为灰色,又可以对函数添加备注,以免被其他人以为这个函数没有被引用而不小心删除。
上面的代码现象如下,点击注册成功回调onRegisterSuccess进而弹出toast,点击登陆成功回调onLoginSuccess弹出toast,一切如我们所想的,按照正常流程来走。
                这里写图片描述

    那么通过Java泛型能不能实现参数泛型化而不用定义RegisterEventLoginEvent等一系列event类呢?继续代码,新建一个GenericsEvent类来实现泛型

public class GenericsEvent<T> {    private T mData;    public void setData(T data) {        mData = data;    }    public T getData() {        return mData;    }}

接着,登陆与注册成功之后通过包装GenericsEvent来发送事件

   private void loginSuccess() {        LoginInfo info = new LoginInfo();        GenericsEvent<LoginInfo> genericsEvent = new GenericsEvent<>();        genericsEvent.setData(info);        EventBus.getDefault().post(genericsEvent);    }    private void registerSuccess() {        RegisterInfo info = new RegisterInfo();        GenericsEvent<RegisterInfo> genericsEvent = new GenericsEvent<>();        genericsEvent.setData(info);        EventBus.getDefault().post(genericsEvent);    }    @SuppressWarnings({"unused","登陆成功之后回调"})    @Subscribe(threadMode = ThreadMode.MAIN)    public void onLoginSuccess(GenericsEvent<LoginInfo> event) {        Log.d("event","login success");    }    @SuppressWarnings({"unused","注册成功之后回调"})    @Subscribe(threadMode = ThreadMode.MAIN)    public void onRegisterSuccess(GenericsEvent<RegisterInfo> event) {        Log.d("event","register success");    }

    通过Java泛型实现,我们来点击注册成功与登陆成功的结果是怎么样的?
                 这里写图片描述

    发现无论是点击注册成功还是登陆成功,onLoginSuccessonRegisterSuccess都被调用了,为什么呢?
    原来Java中的是伪泛型,在编译的时候泛型 T 会被转换为 object 对象,所以无论你是GenericsEvent<LoginInfo>还是GenericsEvent<RegisterInfo>,编译之后都是GenericsEvent类型,在GenericsEvent中有一个object对象来保存LoginInfo或者RegisterInfo,在获取的时候,例如上面的getData(),其实是那个object对象强制转化为对应的T对象;若上述函数onLoginSuccessonRegisterSuccess中调用getData()函数,会抛出强制转化失败的问题,因为两个函数都会被调用,而因为泛型会强制转化类型导致的。
    根据上面所有的,采用Java泛型GenericsEvent<T>,实际上对应的类型都是GenericsEvent,根据上一篇EventBus框架总结之源码分析可以知道,EventBus是根据参数的类型来判断是否回调,因为采用EventBus.getDefault().post(genericsEvent);发布事件,会导致所有的事件都被触发,因此采用Java泛型是不能够解决上面提到的类型泛滥的问题的。


如何解决Event类型泛滥的问题呢?


    既然Java泛型没办法解决这个问题,看来一般通用的方法是没办法解决这个问题了。那么就只能从源码上解决这个问题了。
    这时候想到EventBus的线程模型,还记得我们的线程模型是通过Subscribe这个注解来进行设置的,然后在post()中来判断当前处于哪个线程以及Subscribe中设置的threadMode,从而进行对应的切换。那么,既然通用的泛型没法解决这个问题,那么我们是不是也可以通过Subscribe来设置我们当前函数所能接受的GenericsEvent<T>T的类型,然后在post对应的GenericsEvent的时候,传入对应的type来表示当前postGenericsEvent<T>T的类型,然后在EventBus中通过反射invoke函数之前来判断GenericsEvent<T>中T的类型与当前函数通过Subscribe设置的类型是不是一样,一样就invoke对应的函数,不然就不invoke函数,不做处理。

且看代码,在GenericsEvent<T>中传入对应的type

public interface IGenericsEvent {    Class<?> getGenericsType();}public class GenericsEvent<T> implements IGenericsEvent {    //通过参数来记录当前post的GenericsEvent中T的类型,    //在invoke之前来与Subscribe中设置的type是否一致    //来决定是否invoke函数    private final Class<T> mType;    public GenericsEvent(Class<T> type) {        mType = type;    }    @Override    public Class<?> getGenericsType() {        return mType;    }}

既然要通过Subscribe来设置函数所接受的T的类型,那就需要修改Subscribe的源码,增加genericsType参数

@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface Subscribe {    ThreadMode threadMode() default ThreadMode.POSTING;    boolean sticky() default false;    int priority() default 0;    /**     * 泛型类型,来表示当前函数接受哪种类型     */    Class<?> genericsType() default String.class;}

通过EventBus框架总结之源码分析中我们可以了解到读取的注解中保存在SubscriberMethod中,所以也需要修改SubscriberMethod的代码来保存genericsType,因此也添加genericsType参数,同时修改它的构造函数

/** Used internally by EventBus and generated subscriber indexes. */public class SubscriberMethod {    final Method method;    final ThreadMode threadMode;    final Class<?> eventType;    /**     * 泛型类型,来表示当前函数接受哪种类型     */    final Class<?> genericsType;    final int priority;    final boolean sticky;    String methodString;    public SubscriberMethod(Method method, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky,Class<?> genericsType) {        this.method = method;        this.threadMode = threadMode;        this.eventType = eventType;        this.priority = priority;        this.sticky = sticky;        this.genericsType = genericsType;    }    /..省略无关代码../}

然后再通过findUsingReflectionInSingleClass()函数去读取通过Subscribe注解设置的genericsType保存到SubscriberMethod

private void findUsingReflectionInSingleClass(FindState findState) {        Method[] methods;        /..省略无关的代码../        for (Method method : methods) {            int modifiers = method.getModifiers();            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {                Class<?>[] parameterTypes = method.getParameterTypes();                if (parameterTypes.length == 1) {                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);                    if (subscribeAnnotation != null) {                        Class<?> eventType = parameterTypes[0];                        if (findState.checkAdd(method, eventType)) {                            ThreadMode threadMode = subscribeAnnotation.threadMode();                            //读取注解设置的类型                            Class<?> genericsType = subscribeAnnotation.genericsType();                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky(), genericsType));                        }                    }                }                 /..省略无关的代码../            }        }    }

通过上面的代码修改,通过Subscribe设置的函数支持的参数类型,接下来就是要在invoke函数之前对类型参数读取并比较,只有当类型一样的时候才invoke进行调用,EventBus框架总结之源码分析中分析了post通过一层层调用之后调用到postSingleEventForEventType(),然后postSingleEventForEventType()调用postToSubscription(),而postToSubscription()函数的主要作用是实现EventBus的线程模型切换线程的功能,为了避免不必要的线程切换,因为在切换线程之前进行判断拦截

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {        //线程切换之前进行拦截        if(!needInvokeSubscriber(subscription, event)) {            return;        }        switch (subscription.subscriberMethod.threadMode) {            case POSTING:                invokeSubscriber(subscription, event);                break;            case MAIN:                if (isMainThread) {                    invokeSubscriber(subscription, event);                } else {                    mainThreadPoster.enqueue(subscription, event);                }                break;            case BACKGROUND:                if (isMainThread) {                    backgroundPoster.enqueue(subscription, event);                } else {                    invokeSubscriber(subscription, event);                }                break;            case ASYNC:                asyncPoster.enqueue(subscription, event);                break;            default:                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);        }    }    /**     * 判断是否需要拦截invoke Subscriber     */    private boolean needInvokeSubscriber(Subscription subscription, Object event) {        //首先判断当前的event是否是泛型,若不是泛型类型,则不能        //拦截事件,需要invoke        if(!(event instanceof IGenericsEvent)) {            return true;        }        //若当前event为泛型类型,则读取event中设置的genericsType        IGenericsEvent genericsEvent = (IGenericsEvent) event;        Class<?> genericClass = genericsEvent.getGenericsType();        //与当前函数通过Subscribe注解设置的genericsType类型是否一致,        //当一致是需要invoke,否则进行拦截        return subscription.subscriberMethod.genericsType == genericClass;    }

通过上面的修改,源码基本上修改完了,下面需要去设置方法的注解以及postGenericsEvent<T>时候设置对应的类型genericsType

 private void loginSuccess() {        LoginInfo info = new LoginInfo();        //通过构造函数注入LoginInfo的类型        GenericsEvent<LoginInfo> genericsEvent = new GenericsEvent<>(LoginInfo.class);        genericsEvent.setData(info);        EventBus.getDefault().post(genericsEvent);    }    private void registerSuccess() {        RegisterInfo info = new RegisterInfo();        //通过构造函数注入RegisterInfo的类型        GenericsEvent<RegisterInfo> genericsEvent = new GenericsEvent<>(RegisterInfo.class);        genericsEvent.setData(info);        EventBus.getDefault().post(genericsEvent);    }    //通过Subscribe注解设置genericsType=LoginInfo,即当前函数支持登陆    //LoginInfo类型,其他类型不会回调该函数    @SuppressWarnings({"unused","登陆成功之后回调"})    @Subscribe(threadMode = ThreadMode.MAIN,genericsType = LoginInfo.class)    public void onLoginSuccess(GenericsEvent<LoginInfo> event) {        Toast.makeText(this, "login success", Toast.LENGTH_SHORT).show();    }    //通过Subscribe注解设置genericsType=RegisterInfo,即当前函数支持    //注册RegisterInfo类型,其他类型不会回调该函数    @SuppressWarnings({"unused","注册成功之后回调"})    @Subscribe(threadMode = ThreadMode.MAIN,genericsType = RegisterInfo.class)    public void onRegisterSuccess(GenericsEvent<RegisterInfo> event) {        Toast.makeText(this, "register success", Toast.LENGTH_SHORT).show();    }

通过上述修改,我们看看运行的具体结果如何
                这里写图片描述

上述的结果与最开始的通过定义各种Event的结果是一样的,onLoginSuccessonRegisterSuccess都只被调用了一次,是正确无误的。

通过修改EventBus的源码,使其支持参数泛型化,从此跟各种Event类型说拜拜了!!!

原创粉丝点击