框架手写系列---通过反射手写EventBus框架

Posted 战国剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了框架手写系列---通过反射手写EventBus框架相关的知识,希望对你有一定的参考价值。

一、EventBus原理与分析

EventBus作为常用框架之一,从早期的EventBus2.0到目前3.+,经历了从反射到apt实现的转变,本文以原理分析为主,以反射方式实现早期的2.0框架。

EventBus的核心在于:将被注解的方法(如@Subscribe注解方法),放到Map中。后续post的时候,根据post的参数类型(如Message类)在Map中找到对应的方法,并调用。

将步骤分解后,就是以下两个主要操作:

1、注册:通过特定的注解(如@Subscribe),将被注解的方法,添加到Map中。

2、信息传递:通过post方法,在Map中查找符合要求的方法,并反射调用。

二、手写实现

根据分析,我们再将以上的步骤细化,如下:

1、定义一个注解Subscribe,用于标识哪些方法需要被添加到Map中。

//运行时起效
@Retention(RetentionPolicy.RUNTIME)
//针对方法起效
@Target(ElementType.METHOD)
public @interface Subscribe 
   ThreadModel threadModel() default ThreadModel.ANY;

  其他相关定义:

 

//线程控制枚举:任意,主线程,子线程
public enum ThreadModel 
    ANY,
    MAIN,
    BACKGROUND

2、定义一个单例类(ABus),Map将作为变量放置在该类中,用于存储被Subscribe标记的方法。其中Map的key设为Object类型,可为页面或其他类对象。

//定义一个单例类,也就是通讯总线Bus
public class ABus 
    //用于存储某个类上,有被subscribe标识的方法
    private Map<Object, List<Method>> methods;
    private static volatile ABus aBus;

    private ExecutorService threadPool;
    private Handler handler;

    private ABus() 
        this.methods = new HashMap<>();
        this.threadPool = Executors.newCachedThreadPool();
        this.handler = new Handler(Looper.getMainLooper());
    

    public static ABus getInstance() 
        if (aBus == null) 
            synchronized (ABus.class) 
                aBus = new ABus();
            
        
        return aBus;
    

3、在ABus中,定义register(Object object)方法,入参object可为Activity、Fragment或者其他类对象。后续将根据这个object查找该类上所有有Subscribe标识的方法,并存入到Map中。

//注册类,将会把该类下的subscribe标识的方法,放入到Map中
public void register(Object object) 
        if (object == null) 
            return;
        
        List<Method> methodLs = methods.get(object);
        if (methodLs == null) 
            this.methods.put(object, findSubscriber(object));
        



...

//查找类下,所有被subscribe标识的方法
private List<Method> findSubscriber(Object object) 
        List<Method> methods = new ArrayList<>();
        Class<?> aClass = object.getClass();
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) 
            //判断该方法是否含有Subscribe注解标识
            Subscribe subscribe = declaredMethod.getAnnotation(Subscribe.class);
            if (subscribe == null) 
                continue;
            
            //参数获取
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            //特殊要求:参数只能是一个,不能为空,也不能超过一个
            if (parameterTypes.length != 1) 
                continue;
            
            methods.add(declaredMethod);
        
        return methods;

4、在ABus中,定义post方法post(final Object msg)。post方法将遍历Map中的方法,匹配到与post入参一致的类,并根据线程要求,分发信息。

//post方法
public void post(final Object msg) 
        //获取Map中的所有key
        Set<Object> objects = methods.keySet();
        //如果post的入参,是已注册的key,则进入下方循环
        for (final Object object : objects) 
            //取得该类的所有被注解方法
            List<Method> subScribeMethods = methods.get(object);
            if (subScribeMethods != null) 
                //遍历方法
                for (final Method method : subScribeMethods) 
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    Class<?> type = parameterTypes[0];
                    //post的入参 与 遍历方法参数 类型对比
                    if (type.isAssignableFrom(msg.getClass())) 
                        //线程控制
                        Subscribe subscribe = method.getAnnotation(Subscribe.class);
                        switch (subscribe.threadModel()) 
                            case MAIN:
                                if (isMainThread()) 
                                    //调用方法
                                    invoke(method, msg, object);
                                 else 
                                    handler.post(new Runnable() 
                                        @Override
                                        public void run() 
                                            invoke(method, msg, object);
                                        
                                    );
                                
                                break;
                            case BACKGROUND:
                                if (isMainThread()) 
                                    threadPool.execute(new Runnable() 
                                        @Override
                                        public void run() 
                                            invoke(method, msg, object);
                                        
                                    );
                                 else 
                                    invoke(method, msg, object);
                                
                                break;
                            case ANY:
                            default:
                                invoke(method, msg, object);
                                break;
                        
                    
                
            
        
    


    //调用方法
    private void invoke(Method method, Object msg, Object object) 
        try 
            method.invoke(object, msg);
         catch (IllegalAccessException | InvocationTargetException e) 
            e.printStackTrace();
        
    
   //主线程判断
   private boolean isMainThread() 
        return Looper.myLooper() == Looper.getMainLooper();
    

5、最后,在ABus中,补全unRegister方法,避免内存泄漏,完成整个的设计。

public void unRegister(Object object) 
        if (object == null) 
            return;
        
        if(methods != null)
            methods.remove(object);
        
    

这里写的ABus与EventBus原理类似,功能一致。百多行代码即实现了整体的通讯总线。

以上是关于框架手写系列---通过反射手写EventBus框架的主要内容,如果未能解决你的问题,请参考以下文章

EventBus手写实现事件通信框架 ( 订阅类-订阅方法缓存集合 | 事件类型-订阅者集合 | 订阅对象-事件类型集合 )

EventBus手写实现事件通信框架 ( 实现几个关键的封装类 | 消息中心 | 订阅注解 | 订阅方法封装 | 订阅对象-方法封装 | 线程模式 )

手写web框架之实现Bean容器

手写springIoc框架

《Java手写系列》-手写MyBatis框架

《Java手写系列》-手写MyBatis框架