轉載文章請標明出處http://www.reibang.com/p/233a40a2e0b5
如果你沒有看過我之前的博客建議你先去看看之前的博客 從零開始“擼”出自己的EventBus(一)
或者你已經(jīng)看過我這篇博客從零開始“擼”出自己的EventBus(三)
前一篇博客我們實現(xiàn)了一個簡單MiniBus伤塌。MiniBus雖然已經(jīng)實現(xiàn),但是其仍存在很多缺陷轧铁。
- 不能切換線程每聪,在哪個線程發(fā)送事件,觀察者注冊的方法就會在哪個線程執(zhí)行
- 采用前一篇博客所提到的第二種方案實現(xiàn)齿风,每次有post事件產(chǎn)生會遍歷所有注冊方法药薯,效率很低
- 講觀察者直接存進到Map的key值當中,如果使用的時候忘記在其對應的生命周期取消注冊聂宾,很容易引起內存泄漏
當然果善,它距離我們心中的EventBus還存在很多其他問題,比如不支持注解方式定義觀察者方法巾陕,不支持粘性事件等。但是我們不能一口吃成一個胖子是吧纪他,所有這次我們些先來解決以上三個問題鄙煤,剩下的問題我們在接下來的博客中解決。
夢想起航
這次我們就不再新建工程了茶袒,就在上次新建的工程中新建一個包梯刚,包名就叫middle_bus吧。在這里我再把上次提到的第三種方案拿出來復習一下薪寓。
在第二種方案上做一絲絲改動亡资,就是不以subscriber為Map的key值,而是以event的類型作為Map的key值向叉,這樣的話就不用每次post請求可以直接通過key值取出對應的方法锥腻,都遍歷一遍那么多的方法啦~
為了方便了我再對這種方案進行一丟丟的修改,就是key值直接存儲為event類型對應的Class母谎,因為最后比對的時候還是要利用反射的瘦黑。
嗯,key值類型確定了,value的類型呢幸斥?像以前一樣存為Method匹摇?這樣的話觀察者存到哪兒呢。甲葬。廊勃。。所以我們得自己封裝一個類演顾,這個類里面既要有Method數(shù)據(jù)供搀,也要有觀察者。輕輕松松钠至,我們的第一段代碼就出來了葛虐。
public class BusMethod {
private Method mMethod;
//采用弱引用的方法去存觀察者,可以有效的防止內存泄漏
private WeakReference<Object> mObserverRefer;
public BusMethod() {
}
public BusMethod(Method method, WeakReference<Object> objectWeakReference) {
this.mMethod = method;
this.mObserverRefer = objectWeakReference;
}
public Method getMethod() {
return mMethod;
}
public void setMethod(Method method) {
this.mMethod = method;
}
public WeakReference<Object> getObserverRefer() {
return mObserverRefer;
}
public void setObserverRefer(WeakReference<Object> 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<Class<?>, List<BusMethod>> 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) {
}
}
沖浪~
接下來就是具體方法的實現(xiàn)休溶。
相比較與第二種思路代赁,第三種思路的注冊方法和取消注冊方法都要復雜很多。首先我們來實現(xiàn)注冊方法兽掰。
public void register(@NonNull Object observer) throws IllegalArgumentException {
List<Method> methods = findUsefulMethods(observer);
//如果一個有效方法都沒有則拋出異常
if (null == methods || methods.size() == 0) {
throw new IllegalArgumentException("No useful methods.");
}
for (Method method : methods) {
//因為通過findUsefulMethods方法拿到的方法都只有一個參數(shù)
//所以以下語句沒有問題
Class<?> parameterType = method.getParameterTypes()[0];
List<BusMethod> busMethods = BusHolder.data.get(parameterType);
if (null == busMethods) {
busMethods = new ArrayList<>();
}
BusMethod busMethod = new BusMethod(method, new WeakReference<Object>(observer));
busMethods.add(busMethod);
BusHolder.data.put(parameterType, busMethods);
}
}
/**
* 查找觀察者對象中以onEvent開頭的方法
*/
private List<Method> findUsefulMethods(@NonNull Object observer) {
List<Method> 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
* 并且只有一個參數(shù)窖壕,即視為有效方法
*/
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方法如何實現(xiàn)杉女。
public void unregister(@NonNull Object observer) {
Set<Class<?>> keys = BusHolder.data.keySet();
//拿到所有的key然后遍歷
Iterator<Class<?>> iterator = keys.iterator();
while (iterator.hasNext()) {
Class<?> key = iterator.next();
List<BusMethod> busMethods = BusHolder.data.get(key);
if (null == busMethods || busMethods.size() == 0) {
continue;
}
//拿到key中所有的方法然后遍歷
Iterator<BusMethod> methodIterator = busMethods.iterator();
while (methodIterator.hasNext()) {
BusMethod busMethod = methodIterator.next();
Object savedObserver = busMethod.getObserver();
if (null == savedObserver || savedObserver.equals(observer)) {
methodIterator.remove();
}
}
}
}
可以看出瞻讽,第三種方式的取消注冊的效率要比第二種方法要低很多,凡事有舍必有得熏挎,本來當初分析的時候覺得這種方式要好很多速勇,但是實際上還是存在缺陷。
接下來我們看看post方法如何實現(xiàn)婆瓜。因為我們要解決不能切換線程的問題快集,所有我們先來定義個線程模式的枚舉。
public enum EventThread {
DEFAULT, //默認模式廉白,即在哪個線程調用被調用方法就在哪個線程執(zhí)行
MAIN, //主線程模式个初,不管調用方法在哪個線程,被調用方法均在UI線程執(zhí)行
BACKGROUND, //后臺模式猴蹂,若在UI線程調用則新開一個線程處理院溺,若在非UI線程調用則在原線程處理
ASYNC //異步模式,不管調用方法在哪個線程磅轻,被調用方法均新開一個線程執(zhí)行
}
一步一步的來珍逸,我們先來定義默認模式調用的方法和異步模式調用的方法。
//異步模式調用此方法
private void executeMethodAsync(final Object event, final BusMethod busMethod) {
new Thread() {
@Override
public void run() {
executeMethod(event, busMethod);
}
}.start();
}
//默認模式執(zhí)行此方法
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嗤军,接受到消息后自然就會在主線程中執(zhí)行啦~我們來重寫這個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<BusMethod> busMethods = BusHolder.data.get(eventClass);
if (null != busMethods && busMethods.size() > 0) {
Iterator<BusMethod> 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;
}
}
}
}
//查找方法應該在哪個線程執(zhí)行
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的時候就查找方法的線程叙赚,然后存到我們事先定義的類里面老客,就不用每次執(zhí)行post的時候都去查找一遍其對應的方法啦~
著陸
這里我就不寫測試代碼了,小伙伴們可以根據(jù)思路自己去測試一下bug~