转载文章请标明出处http://www.jianshu.com/p/233a40a2e0b5
如果你没有看过我之前的博客建议你先去看看之前的博客 从零开始“撸”出自己的EventBus(一)
或者你已经看过我这篇博客从零开始“撸”出自己的EventBus(三)
前一篇博客我们实现了一个简单MiniBus。MiniBus虽然已经实现,但是其仍存在很多缺陷。
- 不能切换线程,在哪个线程发送事件,观察者注册的方法就会在哪个线程执行
- 采用前一篇博客所提到的第二种方案实现,每次有post事件产生会遍历所有注册方法,效率很低
- 讲观察者直接存进到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
为了防止内存泄漏,我们采用弱引用的方式去存储观察者。
接下来我们把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~