从零开始“撸”出自己得EventBus(二)

转载文章请标明出处http://www.jianshu.com/p/233a40a2e0b5
如果你没有看过我之前的博客建议你先去看看之前的博客 从零开始“撸”出自己的EventBus(一)
或者你已经看过我这篇博客从零开始“撸”出自己的EventBus(三)

前一篇博客我们实现了一个简单MiniBus。MiniBus虽然已经实现,但是其仍存在很多缺陷。

  1. 不能切换线程,在哪个线程发送事件,观察者注册的方法就会在哪个线程执行
  2. 采用前一篇博客所提到的第二种方案实现,每次有post事件产生会遍历所有注册方法,效率很低
  3. 讲观察者直接存进到Map的key值当中,如果使用的时候忘记在其对应的生命周期取消注册,很容易引起内存泄漏

当然,它距离我们心中的EventBus还存在很多其他问题,比如不支持注解方式定义观察者方法,不支持粘性事件等。但是我们不能一口吃成一个胖子是吧,所有这次我们些先来解决以上三个问题,剩下的问题我们在接下来的博客中解决。

梦想起航

这次我们就不再新建工程了,就在上次新建的工程中新建一个包,包名就叫middle_bus吧。在这里我再把上次提到的第三种方案拿出来复习一下。

在第二种方案上做一丝丝改动,就是不以subscriber为Map的key值,而是以event的类型作为Map的key值,这样的话就不用每次post请求可以直接通过key值取出对应的方法,都遍历一遍那么多的方法啦~

为了方便了我再对这种方案进行一丢丢的修改,就是key值直接存储为event类型对应的Class,因为最后比对的时候还是要利用反射的。
嗯,key值类型确定了,value的类型呢?像以前一样存为Method?这样的话观察者存到哪儿呢。。。。所以我们得自己封装一个类,这个类里面既要有Method数据,也要有观察者。轻轻松松,我们的第一段代码就出来了。

public class BusMethod {
    private Method mMethod;
    //采用弱引用的方法去存观察者,可以有效的防止内存泄漏
    private WeakReference mObserverRefer;

    public BusMethod() {
    }

    public BusMethod(Method method, WeakReference objectWeakReference) {
        this.mMethod = method;
        this.mObserverRefer = objectWeakReference;
    }

    public Method getMethod() {
        return mMethod;
    }

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

    public WeakReference getObserverRefer() {
        return mObserverRefer;
    }

    public void setObserverRefer(WeakReference observerRefer) {
        this.mObserverRefer = observerRefer;
    }


    //提供一个方便的获取观察者的方法,若观察者已被回收,则返回为null
    public Object getObserver() {
        Object observer = null;
        if (null != mObserverRefer) {
            observer = mObserverRefer.get();
        }
        return observer;
    }
}
 
 

为了防止内存泄漏,我们采用弱引用的方式去存储观察者。
接下来我们把EventBus的整体框架写好,这次我们就不叫MiniBus了,我们叫MiddleBus。

public class MiddleBus {
    private static class BusHolder {
        private static MiddleBus instance = new MiddleBus();
        //key为event对象,value为对应的观察者方法集合
        private static Map, List> data = new HashMap<>();
    }

    public static MiddleBus getInstance() {
        return BusHolder.instance;
    }

    //因为注册的观察者可能没有有效的方法,这时我们抛出异常提醒
    public void register(@NonNull Object observer) throws IllegalArgumentException {
    
    }

    public void post(@NonNull Object event) {
        
    }

    public void unregister(@NonNull Object observer) {
        
    }
}

冲浪~

接下来就是具体方法的实现。
相比较与第二种思路,第三种思路的注册方法和取消注册方法都要复杂很多。首先我们来实现注册方法。

public void register(@NonNull Object observer) throws IllegalArgumentException {
        List methods = findUsefulMethods(observer);
        //如果一个有效方法都没有则抛出异常
        if (null == methods || methods.size() == 0) {
            throw new IllegalArgumentException("No useful methods.");
        }

        for (Method method : methods) {
            //因为通过findUsefulMethods方法拿到的方法都只有一个参数
            //所以以下语句没有问题
            Class parameterType = method.getParameterTypes()[0];
            List busMethods = BusHolder.data.get(parameterType);
            if (null == busMethods) {
                busMethods = new ArrayList<>();
            }
            BusMethod busMethod = new BusMethod(method, new WeakReference(observer));
            busMethods.add(busMethod);
            BusHolder.data.put(parameterType, busMethods);
        }
    }
    
    /**
     * 查找观察者对象中以onEvent开头的方法
     */
    private List findUsefulMethods(@NonNull Object observer) {
        List usefulMethods = new ArrayList<>();
        Class observerClass = observer.getClass();
        Method[] methods = observerClass.getDeclaredMethods();
        for (Method method : methods) {
            if (checkIsUsefulMethod(method)) {
                usefulMethods.add(method);
            }
        }
        return usefulMethods;
    }
    
    /**
     * 遍历观察者所有的方法,拿到有效的方法
     * 在此先采用EventBus3.0以前版本的方法,
     * 也就是以onEvent开头的方法,返回值为void
     * 并且只有一个参数,即视为有效方法
     */
    private boolean checkIsUsefulMethod(@NonNull Method method) {
        String methodName = method.getName();
        Class returnType = method.getReturnType();
        Class[] paramsClass = method.getParameterTypes();
        if (methodName.startsWith("onEvent")
                && returnType.equals(Void.TYPE)
                && paramsClass.length == 1) {
            return true;
        }
        return false;
    }
 
 

其中findUsefulMethods方法和checkIsUsefulMethod几乎没有改动。
我们再来看看unregister方法如何实现。

public void unregister(@NonNull Object observer) {
        Set> keys = BusHolder.data.keySet();
        //拿到所有的key然后遍历
        Iterator> iterator = keys.iterator();
        while (iterator.hasNext()) {
            Class key = iterator.next();
            List busMethods = BusHolder.data.get(key);
            if (null == busMethods || busMethods.size() == 0) {
                continue;
            }

            //拿到key中所有的方法然后遍历
            Iterator methodIterator = busMethods.iterator();
            while (methodIterator.hasNext()) {
                BusMethod busMethod = methodIterator.next();
                Object savedObserver = busMethod.getObserver();
                if (null == savedObserver || savedObserver.equals(observer)) {
                    methodIterator.remove();
                }
            }
        }
    }

可以看出,第三种方式的取消注册的效率要比第二种方法要低很多,凡事有舍必有得,本来当初分析的时候觉得这种方式要好很多,但是实际上还是存在缺陷。
接下来我们看看post方法如何实现。因为我们要解决不能切换线程的问题,所有我们先来定义个线程模式的枚举。

public enum EventThread {
    DEFAULT, //默认模式,即在哪个线程调用被调用方法就在哪个线程执行
    MAIN, //主线程模式,不管调用方法在哪个线程,被调用方法均在UI线程执行
    BACKGROUND, //后台模式,若在UI线程调用则新开一个线程处理,若在非UI线程调用则在原线程处理
    ASYNC //异步模式,不管调用方法在哪个线程,被调用方法均新开一个线程执行
}

一步一步的来,我们先来定义默认模式调用的方法和异步模式调用的方法。

//异步模式调用此方法
private void executeMethodAsync(final Object event, final BusMethod busMethod) {
    new Thread() {
        @Override
        public void run() {
            executeMethod(event, busMethod);
        }
    }.start();
}

//默认模式执行此方法
private void executeMethod(Object event, BusMethod busMethod) {
    Object observer = busMethod.getObserver();
    //确定观察者尚未被回收
    if (null != observer) {
        Method method = busMethod.getMethod();
        try {
            if (!method.isAccessible()) method.setAccessible(true);
            method.invoke(observer, event);
        } catch (Exception e) {
           e.printStackTrace();
        }
    }
}

那如果要在UI线程怎么处理呢?我首先想到的是调用runOnUiThread()方法,但是在Fragment和Activity里面可以调用到此方法,那在Service里怎么调用呢?似乎走到了死路。。。。其实,我们不该忘记还有Handler这种东东,在Handler初始化的时候传入一个UI线程的Looper,接受到消息后自然就会在主线程中执行啦~我们来重写这个Handler

public class MainHandler extends Handler {
    private BusMethod mBusMethod;
    private Object mEvent;

    public MainHandler(Object event, BusMethod busMethod) {
        super(Looper.getMainLooper());
        this.mBusMethod = busMethod;
        this.mEvent = event;
    }

    @Override
    public void handleMessage(Message msg) {
        if (null == mBusMethod || null == mEvent) {
            return;
        }

        Object observer = mBusMethod.getObserver();
        //确定观察者尚未被回收
        if (null != observer) {
            Method method = mBusMethod.getMethod();
            try {
                if (!method.isAccessible()) method.setAccessible(true);
                method.invoke(observer, mEvent);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

准备工作完成,我们来攻克最后的post方法。

public void post(@NonNull Object event) {
    Class eventClass = event.getClass();
    List busMethods = BusHolder.data.get(eventClass);
    if (null != busMethods && busMethods.size() > 0) {
        Iterator iterator = busMethods.iterator();
        while (iterator.hasNext()) {
            BusMethod busMethod = iterator.next();
            EventThread eventThead = findMethodThread(busMethod.getMethod());
            switch (eventThead) {
                //主线程模式则交给Handler去处理
                case MAIN:
                    new MainHandler(event, busMethod).sendEmptyMessage(0);
                    break;
                //默认模式交给我们刚才定义好的方法去处理
                case DEFAULT:
                    executeMethod(event, busMethod);
                    break;
                //判断当前线程再进行相应处理
                case BACKGROUND:
                    if (Looper.getMainLooper() == Looper.myLooper()) {
                        executeMethodAsync(event, busMethod);
                    } else {
                        executeMethod(event, busMethod);
                    }
                    break;
                //交给我们事先定义好的方法取出来
                case ASYNC:
                    executeMethodAsync(event, busMethod);
                    break;
                default:
                    executeMethod(event, busMethod);
                    break;
            }
        }
    }
}

//查找方法应该在哪个线程执行
private EventThread findMethodThread(@NonNull Method method) {
    String methodName = method.getName();
    if (null == methodName || methodName.length() < 7) {
        return EventThread.DEFAULT;
    }

    String subName = methodName.substring(7, methodName.length());
    //如果在onEvent后接的Main则视为主线程模式
    if (subName.startsWith("Main")) {
        return EventThread.MAIN;
    }
    //如果在onEvent后接的Background则视为后台线程模式 
    else if (subName.startsWith("Background")) {
        return EventThread.BACKGROUND;
    }
    //如果在onEvent后接的Async则视为异步模式 
    else if (subName.startsWith("Async")) {
        return EventThread.ASYNC;
    }
    //其他一律视为默认模式 
    else {
        return EventThread.DEFAULT;
    }
}

其实我们也可以在register的时候就查找方法的线程,然后存到我们事先定义的类里面,就不用每次执行post的时候都去查找一遍其对应的方法啦~

着陆

这里我就不写测试代码了,小伙伴们可以根据思路自己去测试一下bug~

你可能感兴趣的:(从零开始“撸”出自己得EventBus(二))