EventBus编程思想-实现简易版EventBus

Posted 0 and 1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EventBus编程思想-实现简易版EventBus相关的知识,希望对你有一定的参考价值。

这是编程思想系列的第二篇,这里挑选用的比较多的EventBus讲解一下,不纠结实现细节。先理解设计思想,这也是研究学习开源框架的第一步。
代码:点击GitHub
一个开源框架的出现肯定是有原因的,便捷,性能基本离不开这两点。而EventBus设计的初衷应该是为了便捷。回想自己为什么要使用EventBus就明白了。简单提两句

组件通信方式

Intent:使用它实现组件跳转,并且能携带参数,但只能携带少量数据,同时在跨组件通信时局限性比较大
Handler:使用Handler进行组件通信耦合严重,容易造成内存泄漏
Broadcast:使用广播进行组件通信效率不高,安全性不高
Interface:不能跨线程通信
aidl:使用成本高
看了上面这些,是不是就明白了为什么会有EventBus的原因了吧。
设计框架的过程像是做一个产品。你首先要想一下设计这个框架的目的是什么?需求在哪里?存在即合理,
抛开源码,我们先想一想?如果要做一个组件之间通信工具,如何设计?。既然是为了方便,那么我们想怎么使用它呢?想让它有什么功能。可能会想到下面三点,(也可能想不到,哈哈),能想到的都是好的产品经理。。。
(1)在任意地方,发射数据。
(2)在任意地方,接收数据。
(3)可以指定接收数据的线程。

这个过程,类似产品经理根据需求设计产品的过程。然后我们根据图纸去实现。给我们的产品取一个名字吧:EventBus,其实我更喜欢叫DataBus,名字而已。萝卜青菜,各有所爱。想法有了,如何实现呢?操作起来,是不是可以运用设计模式中的观察者模式。
观察者(订阅者):任意类下的任意方法
被观察者(被订阅者):数据(任意类发射的数据)

实现思路

通过注解形式,标记观察者,然后被观察者提供订阅方法,通过注解扫描,把所有的观察者注入到容器中。被观察者提供发射数据的方法,遍历容器找到接收数据的方法(通过参数类型区分),然后通过反射方式调用观察者的方法。这就实现了被观察者发射数据通知观察者的需求。

代码实现

定义注解:Subscribe

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe 
    ThreadMode threadMode() default ThreadMode.POSTING;

定义EventBus 的行为:IBus。其实就是约定用户如何使用

public interface IBus 

    /**
     * 订阅
     */
    void register(Object subscriber);

    /**
     * 取消订阅,防止内存泄露
     */
    void unRegister(Object subscriber);

    /**
     * 发射数据
     *
     * @param data
     */
    void send(Object data);

订阅者对象模型:SubscriptionMethod

public class SubscriptionMethod 
    private Method method;
    private Class<?> type;
    private ThreadMode threadMode;

    public Method getMethod() 
        return method;
    

    public void setMethod(Method method) 
        this.method = method;
    

    public Class<?> getType() 
        return type;
    

    public void setType(Class<?> type) 
        this.type = type;
    

    public ThreadMode getThreadMode() 
        return threadMode;
    

    public void setThreadMode(ThreadMode threadMode) 
        this.threadMode = threadMode;
    

线程模型枚举

public enum ThreadMode 
    POSTING,
    MAIN,
    BACKGROUND,
    ASYNC

被观察者:EventBus
mSubcription:保存观察者的容器
eventBusHelper 帮助类,做注解扫描,线程管理。 类的职责不能过重(遵循单一职责原则)

public class EventBus implements IBus
/**
* 订阅方法
* key 订阅类
* value 类中的订阅方法集合
*/
private final Map<Object, List> mSubcription;
private final EventBusHelper eventBusHelper;

private EventBus() 
    eventBusHelper = new EventBusHelper();
    this.mSubcription = new HashMap<>();


private static class Instance 
    private static final EventBus instance = new EventBus();


public static EventBus getInstance() 
    return Instance.instance;


@Override
public void register(Object subscriber) 
    List<SubscriptionMethod> methodList = mSubcription.get(subscriber);
    if (methodList == null || methodList.size() == 0) 
        methodList = new ArrayList<>();
        Class<?> clazz = subscriber.getClass();
        while (clazz != null) 
            String className = clazz.getName();
            if (className.startsWith("java.") || className.startsWith("javax.") || className.startsWith("android.")) 
                break;
            
            eventBusHelper.findAnnotationMethod(methodList, clazz);
            //查找父类,有继承关系
            clazz = clazz.getSuperclass();
        
        mSubcription.put(subscriber, methodList);
    


@Override
public void send(Object event) 
    if (mSubcription.size() == 0) 
        return;
    
    Set<Object> set = mSubcription.keySet();
    Iterator<Object> iterable = set.iterator();
    while (iterable.hasNext()) 
        Object next = iterable.next();
        List<SubscriptionMethod> methodList = mSubcription.get(next);
        if (methodList == null) 
            continue;
        
        int size = methodList.size();
        for (int i = 0; i < size; i++) 
            SubscriptionMethod method = methodList.get(i);
            //method.getType()是获取方法参数类型,这里是判断发布的对象类型是否与订阅方法的参数类型一致
            if (method.getType().isAssignableFrom(event.getClass())) 
                eventBusHelper.invokeWithThread(next, method, event);
            
        
    


@Override
public void unRegister(Object target) 
    List<SubscriptionMethod> methodList = mSubcription.get(target);
    if (methodList == null) return;
    methodList.clear();
    mSubcription.remove(target);


帮助类:EventBusHelper,把注解扫描的代码抽离出来,避免被观察者EventBus职责过重,遵循单一职责的原则

class EventBusHelper 
    private final Handler mHandler;
    private final ExecutorService mExecutorService;

    public EventBusHelper() 
        this.mHandler = new Handler(Looper.getMainLooper());
        this.mExecutorService = Executors.newCachedThreadPool();
    

    public void findAnnotationMethod(List<SubscriptionMethod> methodList, Class<?> clazz) 
        Method[] m = clazz.getDeclaredMethods();
        int size = m.length;
        for (int i = 0; i < size; i++) 
            Method method = m[i];
            Subscribe annotation = method.getAnnotation(Subscribe.class);
            if (annotation == null) 
                continue;
            
            Type genericReturnType = method.getGenericReturnType();
            if (!"void".equals(genericReturnType.toString())) 
                throw new EventBusException("方法返回值必须是void");
            
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 1) 
                throw new EventBusException("方法修饰符必须是public,且是非静态,非抽象");
            
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) 
                throw new EventBusException("方法参数个数必须是一个");
            

            //这里就需要实例化订阅方法对象了
            SubscriptionMethod subscriptionMethod = new SubscriptionMethod();
            subscriptionMethod.setMethod(method);
            subscriptionMethod.setType(parameterTypes[0]);
            subscriptionMethod.setThreadMode(annotation.threadMode());
            methodList.add(subscriptionMethod);
        
    


    public void invokeWithThread(final Object next, final SubscriptionMethod method, final Object event) 
        //进行线程切换
        switch (method.getThreadMode()) 
            case POSTING:
                invoke(next, method, event);
                break;
            case MAIN:
                //通过Looper判断当前线程是否是主线程
                //也可以通过线程名判断 "main".equals(Thread.currentThread().getName())
                if (Looper.getMainLooper() == Looper.myLooper()) 
                    invoke(next, method, event);
                 else 
                    mHandler.post(new Runnable() 
                        @Override
                        public void run() 
                            invoke(next, method, event);
                        
                    );
                
                break;
            case BACKGROUND:
                if (Looper.getMainLooper() == Looper.myLooper()) 
                    mExecutorService.execute(new Runnable() 
                        @Override
                        public void run() 
                            invoke(next, method, event);
                        
                    );
                 else 
                    invoke(next, method, event);
                
                break;
            case ASYNC:
                mExecutorService.execute(new Runnable() 
                    @Override
                    public void run() 
                        invoke(next, method, event);
                    
                );
                break;
        
    

    private void invoke(Object next, SubscriptionMethod method, Object event) 
        Method m = method.getMethod();
        try 
            m.invoke(next, event);
         catch (IllegalAccessException e) 
            e.printStackTrace();
         catch (InvocationTargetException e) 
            e.printStackTrace();
        
    

大功告成,开始使用

public class MainActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        EventBus.getInstance().register(this);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() 
            @Override
            public void run() 
                EventBus.getInstance().send(new Message(1, "activity启动了"));
            
        ).start();
    

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void eventBus(Message message) 
        Log.i("event_bus", message.getMsg());
    

    @Override
    protected void onDestroy() 
        super.onDestroy();
        EventBus.getInstance().unRegister(this);
    

以上如果有理解不对,或者代码错误的地方,请大神在评论区留言,谢谢!

以上是关于EventBus编程思想-实现简易版EventBus的主要内容,如果未能解决你的问题,请参考以下文章

RxJava编程思想3-(实现简易版Rxjava,如何实现线程切换)

RxJava编程思想2-(实现简易版Rxjava,如何实现操作符?)

RxJava编程思想2-(实现简易版Rxjava,如何实现操作符?)

RxJava编程思想1-(实现简易版Rxjava,如何基本功能和链式调用?)

EventBus

EventBus设计之禅