EventBus에 대한 자세한 설명은 생략한다. 여기를 참고
EventBus를 View와 BO를 깔끔하게 분리하고, BO의 유닛 테스트를 쉽게 작성하기 위해서 도입한다. 그래서 MVVM 이라는 구현 패턴을 안드로이드에 적용하면서 View와 ViewModel간의 통신에 EventBus를 사용할 예정이다.
나의 EventBus를 구현하면서, 원래 라이브러리의 기능 중 구현하는 기능은 딱 한가지다. UiThread로의 Event 클래스를 콜백하는 기능만 제공한다. 핵심 적인 기능은 RxJava를 이용하기 때문에 대부분의 구현은 RxJava의 구현을 이용하는 코드이다.
Subscribe.java Subscribe annotaion은 Event를 받는 메쏘드에 달아주면 register()에서 자동으로 해당 메쏘드를 등록한다.
package com.mdiwebma.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author djkim
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {
//boolean mainThread() default true;
}
EventBus.java
package com.mdiwebma.base.task;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;
import android.support.annotation.NonNull;
import com.mdiwebma.base.annotation.Subscribe;
/**
* @author djkim
*/
public class EventBus {
private static EventBus defaultEventBus = new EventBus();
private final ConcurrentMap<Object, List<SubscriberAction>> subscriberToActions = new ConcurrentHashMap<>();
private final ConcurrentMap<Class<?>, Subject<?, ?>> eventToSubject = new ConcurrentHashMap<>();
private final ConcurrentMap<Class<?>, Observable<?>> eventToObservable = new ConcurrentHashMap<>();
private static class SubscriberAction {
public final Action1<?> action;
public final Subscription subscription;
SubscriberAction(@NonNull final Action1<?> action, @NonNull final Subscription subscription) {
this.action = action;
this.subscription = subscription;
}
}
public static EventBus getDefault() {
return defaultEventBus;
}
public void register(@NonNull Object subscriber) {
if (subscriberToActions.containsKey(subscriber)) {
return;
}
List<SubscriberAction> actionList = new LinkedList<>();
Method[] methods = subscriber.getClass().getDeclaredMethods();
for (final Method method : methods) {
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if (subscribe != null) {
if (Modifier.isPublic(method.getModifiers()) == false) {
throw new AssertionError("EventBus > Subscribe method is not public. method=" + method.getName());
}
Class<?>[] params = method.getParameterTypes();
if (params.length != 1) {
throw new AssertionError("EventBus > Subscribe method parameter count != 1. method=" + method.getName());
}
if (params[0].equals(Object.class)) {
throw new AssertionError("EventBus > Subscribe method parameter type is Object type. method=" + method.getName());
}
Action1<?> action = createAction(subscriber, method);
Subscription subscription = subscribeAction(params[0], action);
actionList.add(new SubscriberAction(action, subscription));
}
}
subscriberToActions.putIfAbsent(subscriber, actionList);
}
@NonNull
private Action1<?> createAction(@NonNull final Object subscriber, @NonNull final Method method) {
return new Action1() {
@Override
public void call(Object event) {
try {
method.invoke(subscriber, event);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("EventBus > Actino1#call failed: " + subscriber + "." + method + "(" + event + ")", e);
}
}
};
}
@NonNull
private <T> Subscription subscribeAction(@NonNull Class<?> eventClass, @NonNull Action1<T> action1) {
Observable<T> observable = (Observable<T>)eventToObservable.get(eventClass);
if (observable == null) {
Subject<?, ?> subject = eventToSubject.get(eventClass);
if (subject == null) {
subject = PublishSubject.create();
eventToSubject.putIfAbsent(eventClass, subject);
}
observable = (Observable<T>)subject.observeOn(AndroidSchedulers.mainThread());
eventToObservable.putIfAbsent(eventClass, observable);
}
return observable.subscribe(action1);
}
public void unregister(@NonNull Object subscriber) {
List<SubscriberAction> actionList = subscriberToActions.remove(subscriber);
if (actionList != null) {
for (SubscriberAction action : actionList) {
action.subscription.unsubscribe();
}
}
}
public void post(@NonNull Object event) {
Class eventClass = event.getClass();
while (eventClass.equals(Object.class) == false) {
Subject subject = eventToSubject.get(eventClass);
if (subject != null) {
subject.onNext(event);
}
eventClass = eventClass.getSuperclass();
}
}
}