EventBus의 간단한 에디션

9948 단어
이벤트버스는 많은 사람들이 익숙하다고 믿는다. 현재 구글이 공식적으로 JetPack을 내놓았지만 이벤트버스의 디자인 사고방식은 참고할 만하다.다음은 간단한 이벤트버스 사례를 쓰겠습니다.
사실 이벤트버스의 원리는 어렵지 않다. 몇 개의 그룹을 유지한 다음에 대응하는 키에 따라 대응하는 등록 대상을 찾아 방사능을 통해 대응하는 방법을 호출하는 것이다.
EventBus3.0 이전과 이후는 비교적 큰 차이가 있는데 가장 큰 차이는 3.0 이후 apt 재컴파일 기간에 인용 대상을 생성하여 어느 정도 성능을 향상시켰다.
가장 간편한 사용
// 
EventBus.getDefault().register(this);

// 
@Subscribe
public void event(BaseEventBusBeaan message) {
  LogUtils.d("EventBusActivity event");
}

// 
EventBus.getDefault().post(new BaseEventBusBeaan("123", new Bundle()));

// 
EventBus.getDefault().unregister(this);

post 프로세스


우선 우리는 우리의 수요를 정리해야 한다. 우리가 필요로 하는 것은 post 한 대상이 나갈 때 모든 등록 감청 대상의 클래스가 이 통지를 받을 수 있기 때문에 여기에 데이터를 저장할 수 있는 그룹이 필요하다.
//post key, Subscription list value
private Map, CopyOnWriteArrayList> subscriptionsByEventType;

// Subscription 
public class Subscription {

    final Object subscriber;  //activity fragment
    final SubscriberMethod subscriberMethod;  

    public Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
        this.subscriber = subscriber;
        this.subscriberMethod = subscriberMethod;
    }
}

public class SubscriberMethod {

    private String methodName; //  
    private Method method; //  , 
    private ThreadMode threadMode; //  
    private Class> eventType; //  Class, :UserInfo.class
}
subscriptionsByEventType가 생기면 우리는 post()의 발송된 사건에 따라 모든 등록자를 찾아서 다시 list를 훑어보고 하나하나 반사할 수 있다.
public void post(Object event) {
  postSingleEventForEventType(event, event.getClass());
}

// , 
private void postSingleEventForEventType(Object event, Class> eventClass) {
  CopyOnWriteArrayList subscriptions;

  synchronized (this) {
    subscriptions = subscriptionsByEventType.get(eventClass);
  }
  if (subscriptions != null && !subscriptions.isEmpty()) {
    for (Subscription subscription : subscriptions) {
        invokeSubscriber(subscription, event);
    }
  }
}

// 
private void invokeSubscriber(Subscription subscription, Object event) {
  try {
    subscription.subscriberMethod.getMethod().invoke(subscription.subscriber, event);
  } catch (Exception e) {
    e.printStackTrace();
  }
}

상술한 것은 바로 간략판post 과정이다.

Register 프로세스


상술한 post에는 아직 중요한 부분이 하나 있다. 바로 subscriptionsByEventType 데이터의 출처이다. 우리는 자연스럽게 register 과정이라고 생각해야 한다.
다시 subscriptionsByEventType의 키와value를 살펴보면 이 값들은 대부분 아래의 함수에서 얻을 수 있다는 것을 알 수 있다.
@Subscribe
public void event(BaseEventBusBeaan message) {
  LogUtils.d("EventBusActivity event");
}

그래서 우리는 클래스의 모든 방법을 훑어보고 @Subscribe 주석된 함수를 찾아서 저장해야 한다.
이것은 apt 방안으로 컴파일 과정에서 모든 종류를 두루 훑어보고 @Subscribe에 주석된 함수를 찾아 일정한 양식에 따라 저장하면 다음과 같은 종류가 생성됩니다.
// , 
//  @Subscribe SUBSCRIBER_INDEX 。
//key , MainActivity,value , 。
public final class MyEventBusIndex implements SubscriberInfoIndex {
  private static final Map SUBSCRIBER_INDEX;

  static {
    SUBSCRIBER_INDEX = new HashMap();
    putIndex(new SimpleSubscriberInfo(EventBusActivity2.class,
            new SubscriberMethod[] {
                    new SubscriberMethod(EventBusActivity2.class, "event", BaseEventBusBeaan.class, ThreadMode.POSTING, 0, false),
                    new SubscriberMethod(EventBusActivity2.class, "sticky", UserInfo.class, ThreadMode.POSTING, 2, true),
                    new SubscriberMethod(EventBusActivity2.class, "sticky2", UserInfo.class, ThreadMode.POSTING, 2, true)}
                    ));
  }

  private static void putIndex(SubscriberInfo info) {
    SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
  }

  @Override
  public SubscriberInfo getSubscriberInfo(Class subscriberClass) {
    return SUBSCRIBER_INDEX.get(subscriberClass);
  }
}
MyEventBusIndex가 생기면 register절차를 시작합니다.
public void register(Object subscriber) {
  Class> subscriberClass = subscriber.getClass();
  List subscriberMethods = findSubscriberMethods(subscriberClass);
    
  // subscriptionsByEventType ,
  for (SubscriberMethod method : subscriberMethods) {
    subscribe(subscriber, method);
  }
}

//1. subscriberClass methodBySubscriber 
private List findSubscriberMethods(Class> subscriberClass) {
  List subscriberMethods = methodBySubscriber.get(subscriberClass);
  if (subscriberMethods != null) return subscriberMethods;

  subscriberMethods = findByAPT(subscriberClass);
  if (subscriberMethods != null) {
    methodBySubscriber.put(subscriberClass, subscriberMethods);
  }

  return subscriberMethods;
}

//2. subscriberInfoIndex ,subscriberInfoIndex MyEventBusIndex 
private List findByAPT(Class> subscriberClass) {
  if (subscriberInfoIndex == null) {
    throw new RuntimeException(" ");
  }
  SubscriberInfo subscriberInfo = subscriberInfoIndex.getSubscriberInfo(subscriberClass);
  if (subscriberInfo != null) return Arrays.asList(subscriberInfo.getSubscriberMethods());
  return null;
}

이어서 옮겨다니기 시작subscriberMethods(구독자마다 한 가지 방법만 추가된 것은 아니기 때문@Subscribe 주석)
for (SubscriberMethod method : subscriberMethods) {
  subscribe(subscriber, method);
}

// post  subscriptionsByEventType  。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
  Class> eventType = subscriberMethod.getEventType();
  CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
  if (subscriptions == null) {
    subscriptions = new CopyOnWriteArrayList<>();
    subscriptionsByEventType.put(eventType, subscriptions);
  }

  Subscription subscription = new Subscription(subscriber, subscriberMethod);
  subscriptions.add(i, subscription);

  // ,unregister 
  List> subscribeEvents = typeBySubscriber.get(subscriber);
  if (subscribeEvents == null) {
    subscribeEvents = new ArrayList<>();
    typeBySubscriber.put(subscriber, subscribeEvents);
  }
  subscribeEvents.add(eventType);
}

여기까지 왔는데 사실 간단한 절차 하나가 이미 통했다.

대략적인 절차를 총결해 보다

  • apt를 통해 컴파일링 기간에 @Subscribe에 주석된 모든 함수를 MyEventBusIndex 대상에 추가합니다.
  • register 과정에서 생성된 데이터subscriptionsByEventType.
  • post 과정에서 subscriptionsByEventType 데이터로 대응하는 함수를 찾은 다음에 반사하는 방식으로 호출한다.

  • 우선 순위 문제


    이 문제도 간단하다. 데이터를 삽입할 때 우선순위를 판단하면 된다.
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
      Class> eventType = subscriberMethod.getEventType();
      CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
      if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
      }
    
      Subscription subscription = new Subscription(subscriber, subscriberMethod);
      
      // 
      int size = subscriptions.size();
      for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.getPriority() > subscriptions.get(i).subscriberMethod.getPriority()) {
          if (!subscriptions.contains(subscription)) subscriptions.add(i, subscription);
          break;
        }
      }
    
      // ,unregister 
      List> subscribeEvents = typeBySubscriber.get(subscriber);
      if (subscribeEvents == null) {
        subscribeEvents = new ArrayList<>();
        typeBySubscriber.put(subscriber, subscribeEvents);
      }
      subscribeEvents.add(eventType);
    }
    

    점성 이벤트


    일반 이벤트는 먼저 등록하고 발송합니다.점성 이벤트는 반대로 먼저 보내고 나중에 등록한다.
    우리는 단지 순서를 바꾸기만 하면 된다.발송할 때 이벤트를 저장한 다음 register할 때 적당한 이벤트가 있는지 확인하세요
    public void postSticky(Object event) {
      stickyEvents.put(event.getClass(), event);
    }
    
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
            ....
        
        // 
        sticky(subscriberMethod, eventType, subscription);
    }
    
    private void sticky(SubscriberMethod subscriberMethod, Class> eventType, Subscription subscription) {
      if (subscriberMethod.isSticky()) {
        Object event = stickyEvents.get(eventType);
        if (event != null) {
          postToSubscription(subscription, event);
        }
      }
    }
    

    마지막으로 postToSubscription 코드를 추가합니다.
    private void postToSubscription(final Subscription subscription, final Object event) {
      switch (subscription.subscriberMethod.getThreadMode()) {
        case POSTING: //  、 
          invokeSubscriber(subscription, event);
          break;
        case MAIN:
          // 
          if (Looper.myLooper() == Looper.getMainLooper()) {
            invokeSubscriber(subscription, event);
          } else {
            // 
            handler.post(new Runnable() {
              @Override
              public void run() {
                invokeSubscriber(subscription, event);
              }
            });
          }
          break;
        case ASYNC:
          // 
          if (Looper.myLooper() == Looper.getMainLooper()) {
            executorService.execute(new Runnable() {
              @Override
              public void run() {
                invokeSubscriber(subscription, event);
              }
            });
          } else {
            invokeSubscriber(subscription, event);
          }
          break;
      }
    }
    
    private void invokeSubscriber(Subscription subscription, Object event) {
      try {
        subscription.subscriberMethod.getMethod().invoke(subscription.subscriber, event);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    

    클릭하여 소스 코드 보기

    좋은 웹페이지 즐겨찾기