轻量级的EventBus简单实现

EventBus 简介

EventBus是一种用于Android的事件发布-订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。

1.1 三个角色

  1. Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。
  2. Subscriber:事件订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING。
  3. Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。

1.2 四种线程模型

EventBus3.0有四种线程模型,分别是:

  1. POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
  2. MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
  3. BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
  4. ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。

 

先看下以下的一种场景:

现在有在同一个进程的ActivityA和ActivityB,现在的简单需求是点击ActivityB中的按钮,修改ActivityA中TextView中的内容.

public class ActivityA extends Activity implements View.OnClickListener {
    private TextView textView;
    public static ActivityA activityA;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        textView = findViewById(R.id.tv);
        Button button = findViewById(R.id.btnStartB);
        button.setOnClickListener(this);
        activityA = this;
    }

    public void setTextView(){
        textView.setText("ActivityB set ActivityA");
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setClass(this,ActivityB.class);
        startActivity(intent);
    }

 

public void onDestroy(){
       super.onDestroy();
        activityA = null;//防止内存漏洞
    }
}

public class ActivityB extends Activity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        Button button = findViewById(R.id.btn);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        ActivityA.activityA.setTextView();
    }
}

以上的代码是以一种,这种方式的问题是直接引用在ActivityB引用了ActivityA类,耦合度高。

现在做以下的优化:

新增ActivityGloble
public class ActivityGloble {
    private  ActivityA activityA;
    public static ActivityGloble activityGloble = new ActivityGloble();
    public void setActivityA(ActivityA activityA){
        this.activityA = activityA;
    }

    public void setTextView(){
        activityA.setTextView();
    }
}

public class ActivityA extends Activity implements View.OnClickListener {
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        textView = findViewById(R.id.tv);
        Button button = findViewById(R.id.btnStartB);
        button.setOnClickListener(this);
        ActivityGloble.activityGloble.setActivityA(this);
      //  activityA = this;
    }

    public void setTextView(){
        textView.setText("ActivityB set ActivityA");
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setClass(this,ActivityB.class);
        startActivity(intent);
    }

    public void onDestroy(){
        super.onDestroy();
        ActivityGloble.activityGloble.setActivityA(null);//防止内存漏洞
    }
}


public class ActivityB extends Activity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        Button button = findViewById(R.id.btn);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        ActivityGloble.activityGloble.setTextView();
    }
}

现在看起来已经优雅了很多了,但是还有以下一些问题要:

  1. 如果不是ActivityA变成了其他的ActivityC或者其他的类,要怎么处理?
  2. 现在是只有一个方法,如果方法多了,ActivityGloble是要手动增加方法的,如何才能做到自动获取方法?

针对以上两点,要做如下的改动:将要引用的类改成Object类型,要被执行的方法通过反射获取。

public class ActivityGloble {
    private  Object subscriber;//要订阅的类,
    public static ActivityGloble activityGloble = new ActivityGloble();
    private HashMap<String,Method> methodMap = new HashMap<>();//需要被执行方法map
    /*
     * @param subscriber//要订阅的类
     * @param postMethodNames,订阅类要执行的方法列表
     */
    public void register(Object subscriber,String[] postMethodNames){
        this.subscriber = subscriber;
        if(subscriber == null){
            return;
        }
        if(postMethodNames != null){
            Class<?> subscriberClass = subscriber.getClass();//获取类
            Method[] methods = subscriberClass.getDeclaredMethods();//获取所有方法
            for(int i = 0;i < methods.length;i++){
                for(int j = 0;j < postMethodNames.length;j++){
                    if(methods[i].getName().equals(postMethodNames[j])){
                        StringBuilder paramTypeBuilde = null;//用于处理函数的重载
                        Class<?>[] paramTypes = methods[i].getParameterTypes();
                        if(paramTypes != null && paramTypes.length > 0){
                            paramTypeBuilde = new StringBuilder();
                            for(int k = 0; k < paramTypes.length;k++){
                                paramTypeBuilde.append(paramTypes[k].getName());//取出函数对应的参数
                            }
                        }
                        if(paramTypeBuilde == null){
                            methodMap.put(postMethodNames[j],methods[i]);//如果这个函数没有参数
                        }else{
                            //如果有参数,参数也和方法一起,作为一个key
                            //比如setTextView(String arg1,Integer arg3)会转化为 setTextViewjava.lang.Stringjava.lang.Integer                      methodMap.put(postMethodNames[j]+paramTypeBuilde.toString(),methods[i]);
                        }
                    }
                }
            }
        }
    }
    /**
     * @param methodName
     * @param args,要传入的参数
     */
    public void post(String methodName, Object... args) {
        if(args != null && args.length > 0){

//如果有参数,对方法进行拼接
            StringBuilder builder = new StringBuilder();
            builder.append(methodName);
            for(int i = 0;i < args.length;i++){
                builder.append(args[i].getClass().getName());
            }
            methodName = builder.toString();
        }
        Method method = methodMap.get(methodName);
        if(method != null){
            try {
                if(args == null || args.length == 0){
                    method.invoke(subscriber);
                }else{
                    method.invoke(subscriber,args);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

对于post方法,这里要进行说明下:

因为是Object... args这样的方式传参,所以会导致int,long,float,double,boolean,byte转为Integer,Long,Float,Double,Boolean,Byte

所以为了处理这个问题,所以要声名的函数int,long,float,double,boolean,byte这些类型都要转为Integer,Long,Float,Double,Boolean,Byte。

 

具体的调用:

public class ActivityA extends Activity implements View.OnClickListener {
    private TextView textView;
    private String[] methods = {"setTextView","setTextView1"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        textView = findViewById(R.id.tv);
        Button button = findViewById(R.id.btnStartB);
        button.setOnClickListener(this);
        ActivityGloble.activityGloble.register(this,methods);
      //  activityA = this;
    }

    public void setTextView(){
        textView.setText("ActivityB set ActivityA");
    }
    public void setTextView1(String arg1,String arg2){
        TextView textView = findViewById(R.id.tv1);
        textView.setText("ActivityB set ActivityA "+arg1+" "+arg2);
    }
    public void setTextView(String arg1,Integer arg3){
        TextView textView = findViewById(R.id.tv2);
        textView.setText("ActivityB set ActivityA "+arg1+" "+" arg3 is "+3);
    }
    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setClass(this,ActivityB.class);
        startActivity(intent);
    }

    public void onDestroy(){
        super.onDestroy();
        ActivityGloble.activityGloble.register(null,null);//防止内存漏洞
    }
}

public class ActivityB extends Activity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        Button button = findViewById(R.id.btn);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        ActivityGloble.activityGloble.post("setTextView");
        ActivityGloble.activityGloble.post("setTextView1","qaa","bbb");
        ActivityGloble.activityGloble.post("setTextView","wwww",1);
    }
}

这样看来解决之前提出的两个问题。

 

现在又有新的问题出现了:

1.是否可以注册多个类?

2.这里是手动把要注册的方法定好了,有没有更方式,让我们不需要提前写好方法,就能知道哪个方法要被执行?

3.执行的时候,可不可以只传个参数就能知道调用哪个方法?

 

分析问题1:

参照了EventBus里的源码,可以用缓存做处理,如下:

HashMap<Object,HashMap<String,SubscribleMethod>> cacheMap = new HashMap<>();

Object来作Key,就是订阅的类,HashMap<String,SubscribleMethod>这个是类对应订阅的方法。

代码如下:

HashMap<String,SubscribleMethod> methodMap = cacheMap.get(object);//先从缓存中查找。
if(methodMap == null){//缓存里没有找到
   methodMap = getSubscribleMethodMap(object);//获取方法map,
   cacheMap.put(object,methodMap);//将方法map放入到缓存中

}

 

分析下问题2:

在反射Method中有一个方法:

public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
    return super.getAnnotation(annotationClass);
}

这个方法可以获取写在方法上的注解,以此来判断这个方法是否有用到这个注解。所以可以通过注解的方法来查找哪个方法需要被执行。

 

对于问题3,如果每个函数传入的参数都不一样,这样是可以做到的。但是如果是两个不同的函数,有相同的参数,这时是不参区分的。还有就是如果注册了多个类,每个类都有相同的函数名和参数,这个也是不好区分的。

所以要做到以下三点:

1.定义函数的时候,尽量不要有相同的参数

2.如果同一个类里多个函数有相同的参数,调用的时候还是要把函数名给传递。

3.如果注册了多个类,且类里还要相同的函数,这里还是要传入类名。

 

现在来看下具体的实现:

先声名一个注解类:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBusSubscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;//判断是运行在哪个线程
}

ThreadMode 是判断函数的运行的类型,具体是参照EventBus源码里的:

public enum ThreadMode {
    /**
     * Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery
     * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
     * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers
     * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
     */
    POSTING,

    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is
     * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event
     * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread.
     * If not on Android, behaves the same as {@link #POSTING}.
     */
    MAIN,
    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},
     * the event will always be queued for delivery. This ensures that the post call is non-blocking.
     */
    MAIN_ORDERED,
    /**
     * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods
     * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
     * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to
     * return quickly to avoid blocking the background thread. If not on Android, always uses a background thread.
     */
    BACKGROUND,
    /**
     * Subscriber will be called in a separate thread. This is always independent from the posting thread and the
     * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should
     * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
     * of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus
     * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications.
     */
    ASYNC}

 

//对所要订阅的方法进行一些封装。
public class SubscribleMethod {
    //注册方法
    private Method method;
    //线程类型
    private ThreadMode threadMode;

    //参数类型
    private Class<?>[] paramTypes;


    public SubscribleMethod(Method method, ThreadMode threadMode, Class<?>[] paramTypes) {
        this.method = method;
        this.threadMode = threadMode;
        this.paramTypes = paramTypes;
    }

    /**
     *
     * 获取方法名字
     */
    public String getMethodNameKey(){
        String methodName = method.getName();
        if(paramTypes == null || paramTypes.length ==0){
           // return method.getName();
        }else{
            StringBuilder builder = new StringBuilder();
            builder.append(methodName);
            for(int k = 0; k < paramTypes.length;k++){
                builder.append(paramTypes[k].getName());
            }
            methodName = builder.toString();
        }
        return  methodName;
    }
    /**
     *
     * 参数拼接的字段
     */
    public String getParamTypesNameKey(){
        if(paramTypes == null || paramTypes.length ==0){
            // return method.getName();
            return "";
        }else{
            StringBuilder builder = new StringBuilder();
            for(int k = 0; k < paramTypes.length;k++){
                builder.append(paramTypes[k].getName());
            }
            return builder.toString();
        }
    }
    public Method getMethod() {
        return method;
    }

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

    public ThreadMode getThreadMode() {
        return threadMode;
    }

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

    public Class<?>[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class<?>[] paramTypes) {
        this.paramTypes = paramTypes;
    }
}

 

public class EventBus {
    private static EventBus instance = new EventBus();
    private  Object subscriber;//要订阅的类,
    private HashMap<Object,HashMap<String,SubscribleMethod>> cacheMap ;
    private Handler handler;//用于处理需要在主线程中执行的事件 
    private ExecutorService executorService;//线程池用于处理异步事件
    public static EventBus getDefault() {
        if(instance == null){
            synchronized (EventBus.class){
                if(instance == null){
                    instance = new EventBus();
                }
            }
        }
        return instance;
    }

    public EventBus(){
        this.cacheMap = new HashMap<>();
        handler = new Handler(Looper.getMainLooper());
        executorService = Executors.newCachedThreadPool();
    }
    public void regisg(Object object){
        if(object == null){
            return;
        }
        HashMap<String,SubscribleMethod> methodMap = cacheMap.get(object);//先从缓存中查找。
        if(methodMap == null){
            methodMap = getSubscribleMethodMap(object);//获取方法map,
            cacheMap.put(object,methodMap);//将方法map放入到缓存中

        }

    }

    private HashMap<String,SubscribleMethod> getSubscribleMethodMap(Object subscriber){
        Class<?> subscriberClass = subscriber.getClass();//获取类
        HashMap<String,SubscribleMethod> methodMap = new HashMap<>();
        while (subscriberClass != null) {
            //判断分类是在那个包下,(如果是系统的就不需要)
            String name = subscriberClass.getName();
            if (name.startsWith("java.") ||
                    name.startsWith("javax.") ||
                    name.startsWith("android.") ||
                    name.startsWith("androidx.")) {
                break;
            }
            Method[] declaredMethods = subscriberClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                //找相应的注解
                EventBusSubscribe annotation = method.getAnnotation(EventBusSubscribe.class);
                if(annotation == null){//注解没有找到
                    continue;
                }
                ThreadMode threadMode = annotation.threadMode();
                Class<?>[] paramTypes = method.getParameterTypes();
                SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,paramTypes);
                methodMap.put(subscribleMethod.getMethodNameKey(),subscribleMethod);
            }
            subscriberClass = subscriberClass.getSuperclass();//遍历父类
        }
        return methodMap;
    }


    public void post(final Object... event){
        postClasssMethod(null,null,event);
    }

    public void postMethod(final String medhodName,final Object... event){
        postClasssMethod(null,medhodName,event);
    }

    /**
     *
     * @param className 执行对应的类
     * @param methodName 执行对应的方法
     * @param event 参数
     */
    public void postClasssMethod(final String className,final String methodName,final Object... event){
        Set<Object> set = cacheMap.keySet();
        Iterator<Object> iterator = set.iterator();
        Object invokeObj = null;
        SubscribleMethod subscribleMethod= null;
        String myClassName = "";
        if(className == null){
        }else{
            myClassName = className;
        }
        boolean isFindClass = false;
        String myMethodName = methodName;
        if(methodName == null ){
            myMethodName = "";
        }
        while (iterator.hasNext()) {
            //拿到注册类
            final Object nextClassObj = iterator.next();
            if(nextClassObj.getClass().getName().equals(myClassName)) {//通过类名找到对应的类
                isFindClass = true;
            }
            HashMap<String,SubscribleMethod> methodHashMap = cacheMap.get(nextClassObj);
            String tempMethodName = getMethodName(myMethodName,event);
            for (Map.Entry<String, SubscribleMethod> entry : methodHashMap.entrySet()) {
                subscribleMethod = entry.getValue();
                String paramTypesName =subscribleMethod.getParamTypesNameKey();//获取参数的拼接字符
                String methodNameKey = subscribleMethod.getMethodNameKey();
                if(methodNameKey.equals(tempMethodName)){//通过类名以及参数找到对应的方法了,执行完方法的,就退出循环了
                    invokeObj = nextClassObj;
                    invoke(subscribleMethod,invokeObj,event);
                    break;
                }else if(paramTypesName.equals(tempMethodName)){//通过参数找到对应的方法
                    if(subscribleMethod != null){
                        invokeObj = nextClassObj;
                        invoke(subscribleMethod,invokeObj,event);
                    }
                }
            }
            if(isFindClass){//如果找到对应的类,执行完对应的类中的方法就退出不再执行了
                break;
            }
        }
    }

    private String getMethodName(String methodName,Object... args){
        StringBuilder builder = new StringBuilder();
        builder.append(methodName);
        if(args != null && args.length > 0){
            //如果有参数,对方法进行拼接
            for(int i = 0;i < args.length;i++){
                builder.append(args[i].getClass().getName());
            }
           // methodName = builder.toString();
        }
        return builder.toString();
    }

    private void invoke(SubscribleMethod subscribleMethod, final Object classObj, final Object ... params) {
        final Method method = subscribleMethod.getMethod();
        ThreadMode threadMode = subscribleMethod.getThreadMode();
        switch (threadMode){
            case MAIN:
                if(Looper.myLooper() == Looper.getMainLooper()){
                    invokeMethod(method,classObj,params);
                }else{
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            invokeMethod(method,classObj,params);
                        }
                    });
                }
                break;
            case POSTING:
                invokeMethod(method,classObj,params);
                break;
            case ASYNC:
                //post方法执行在主线程中
                if(Looper.myLooper() == Looper.getMainLooper()){
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            invokeMethod(method,classObj,params);
                        }
                    });
                } else {
                    //post方法执行在子线程中
                    invokeMethod(method,classObj,params);
                }
                break;
        }

    }

    private void invokeMethod(Method method,Object classObj, Object ... params){
        try {
            method.invoke(classObj, params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    //取消注册
    public void unregister(Object subscriber) {
        Object hashMap = cacheMap.get(subscriber);
        //如果获取到
        if (hashMap != null) {
            cacheMap.remove(subscriber);
        }
    }
}

 

这里说明下postpostMethod,postClasssMethod,这三个方法。

post方法会执行注册过类中相同参数的所有方法。

postMethod,如果有传递方法的名称,则会根据方法名称和参数名字查找到对应的方法进行执行。

postClasssMethod如果有同时传递注册过的类名以及其中的方法,则只会执行类中对应的方法,

如果只传入类名而没有传入方法,则执行类中有相同参数的方法。

 

最后

这个只是参考了eventBus源码,实现了一些基本的功能很多EvenbBus源码中的功能没有实现,如事件的顺序执行、切换到后台的线程等。EventBus源码中,传递事件的参数只有一个,这里代码的实现可以传递多个参数,也算是一个不同点。

对应代码的地址:https://github.com/fzhFidt/eventBus

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值