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

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,一切如我们所想的,按照正常流程来走。
                EventBus框架总结之支持泛型参数_第1张图片

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

public class GenericsEvent {

    private T mData;

    public void setData(T data) {
        mData = data;
    }

    public T getData() {
        return mData;
    }
}

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

   private void loginSuccess() {
        LoginInfo info = new LoginInfo();
        GenericsEvent genericsEvent = new GenericsEvent<>();
        genericsEvent.setData(info);
        EventBus.getDefault().post(genericsEvent);
    }

    private void registerSuccess() {
        RegisterInfo info = new RegisterInfo();
        GenericsEvent genericsEvent = new GenericsEvent<>();
        genericsEvent.setData(info);
        EventBus.getDefault().post(genericsEvent);
    }

    @SuppressWarnings({"unused","登陆成功之后回调"})
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onLoginSuccess(GenericsEvent event) {
        Log.d("event","login success");

    }

    @SuppressWarnings({"unused","注册成功之后回调"})
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onRegisterSuccess(GenericsEvent event) {
        Log.d("event","register success");
    }

    通过Java泛型实现,我们来点击注册成功与登陆成功的结果是怎么样的?
                 EventBus框架总结之支持泛型参数_第2张图片

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


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


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

且看代码,在GenericsEvent中传入对应的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时候设置对应的类型genericsType

 private void loginSuccess() {
        LoginInfo info = new LoginInfo();
        //通过构造函数注入LoginInfo的类型
        GenericsEvent genericsEvent = new GenericsEvent<>(LoginInfo.class);
        genericsEvent.setData(info);
        EventBus.getDefault().post(genericsEvent);
    }

    private void registerSuccess() {
        RegisterInfo info = new RegisterInfo();
        //通过构造函数注入RegisterInfo的类型
        GenericsEvent 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 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 event) {
        Toast.makeText(this, "register success", Toast.LENGTH_SHORT).show();
    }

通过上述修改,我们看看运行的具体结果如何
                EventBus框架总结之支持泛型参数_第3张图片

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

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

你可能感兴趣的:(开源框架,EventBus)