[Android] EventBus보다 빠르고 간단한 MessageBus !!

Android에서 이벤트를 전달하는 방법은 다양합니다. 일반적인 Callback이나 Listener를 통해 전달하는 방법이 있고, Broadcast를 통해서 전달하거나 Intent를 통해 전달을 하는 등, 다양한 방법들이 있습니다.

하지만, 앱이 복잡해짐에 따라 Listener를 깊숙하게 전달해야하는 문제들이 있었습니다. Broadcast를 하기에는 너무 무거운 감이 있고, 기타 방법들도 마땅치 않은 경우들이 많습니다.

이러한 문제를 해결하기 위해서 다양한 개발자들이 여러 해법들을 제시해왔고, 그 중 인기를 얻고 있는 EventBus(https://github.com/greenrobot/EventBus) 가 있습니다.

저 역시 EventBus를 사용하기 위해 검토를 해봤는데, 제가 봤을 때 문제점과 우려되는 점들이 있었습니다.

EventBus의 문제 + 우려
1. 전달하는 Event의 형태는 항상 Object이다.
  - instanceof 로 같은 Class를 등록해둔 Subscriber는 모두 받게 된다.
  - 가볍게 이벤트의 발생만 알리고 싶어도 커스텀 클래스를 정의해야한다.
2. 정의된 Annotation을 통해 Subscriber로 등록하지만, Runtime에 Annotation이 리플렉션으로 처리되기 때문에, 약간의 속도저하를 예상할 수 있다.
  - 한두개 정도의 Subscriber의 등록은 큰 문제 없겠지만, 동시에 Subscriber로 등록하는 컴포넌트들이 많다면(40~60개 수준?), 약간은 걱정되는 부분이다.


이 두가지 문제를 해결하기 위해 MessageBus를 만들게 되었습니다.
( Github : https://github.com/UnsignedUSB/MessageBus )
1. MessageBus에서는 Event를 integer형으로 던질 수 있습니다.
2. MessageBus는 Annotation Processor로 돌아가기 때문에, Build time에 Code가 생성됩니다.
  - 생성되는 코드 내부로직을 최대한 단순하게 만들어서, 성능저하가 발생하지 않습니다.
  - 내부로직이 단순하기 때문에, 개발자가 이해하고 사용하는데 어려움이 없습니다.    


사용방법은 Github를 참고하시면 되지만,
다음 글에서 사용방법을 조금 자세하게 다뤄보겠습니다.


성능비교!! (EventBus vs MessageBus)

아래와 같이 간단한 TestCode를 작성해서 성능비교를 해봤습니다.
Test 방법은 EventBus와 MessageBus로 5000번 이벤트를 발생시켜서
Event 발생과 그 전달까지 걸리는 시간의 합계를 비교해 봤습니다.
(Test Device : 갤럭시S6 Android 7.0)

※ Hang 걸리는 것을 방지하기 위해서 다음 이벤트를 보내기까지 약간의 딜레이를 줬고, 그것은 총 시간에 포함되지 않았습니다.
※ 다른 조건을 최대한 통일 시키기 위해서, EventBus를 이용시 사용되는 Model 클래스를 MessageBus에서도 사용했습니다.

public class PerformanceTestActivity extends AppCompatActivity implements View.OnClickListener{

    private static final int MESSAGE_TEST = 0x00010001;

    private Button messageBusTest;
    private Button eventBusTest;

    private Handler handler;

    private long deltaSum = 0;
    private int currentCount = 0;
    private final int testCount = 5000;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_performance_test);

        messageBusTest = (Button) findViewById(R.id.messageBusTest);
        eventBusTest = (Button) findViewById(R.id.eventBusTest);

        messageBusTest.setOnClickListener(this);
        eventBusTest.setOnClickListener(this);

        handler = new Handler();
        MessageBus.getInstance().register(this);
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        MessageBus.getInstance().unregister(this);
        EventBus.getDefault().unregister(this);
    }

    @Override
    public void onClick(View view) {
        deltaSum = 0;
        currentCount = 0;

        messageBusTest.setEnabled(false);
        eventBusTest.setEnabled(false);
        switch (view.getId()) {
            case R.id.messageBusTest:
                sendMessageToMessageBus();
                break;
            case R.id.eventBusTest:
                sendMessageToEventBus();
                break;
        }
    }


    @Subscribe(events = MESSAGE_TEST)
    public void onMessageBusReceived(EventBusModel model) {
        deltaSum += System.nanoTime() - model.startTimeNano;
        currentCount++;
        if(currentCount < testCount) {
            sendMessageToMessageBus();
            ((TextView)findViewById(R.id.textView)).setText("MessageBus : " + currentCount + ",  " + deltaSum);
        } else {
            ((TextView)findViewById(R.id.resultTV)).append("MessageBus : " + deltaSum + "\n");
            messageBusTest.setEnabled(true);
            eventBusTest.setEnabled(true);
        }
    }
    private void sendMessageToMessageBus() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                EventBusModel model = new EventBusModel();
                model.startTimeNano = System.nanoTime();
                MessageBus.getInstance().handle(MESSAGE_TEST, model);
            }
        }, 20);
    }


    @org.greenrobot.eventbus.Subscribe
    public void onEventBusReceived(EventBusModel model) {
        deltaSum += System.nanoTime() - model.startTimeNano;
        currentCount++;
        if(currentCount < testCount) {
            sendMessageToEventBus();
            ((TextView)findViewById(R.id.textView)).setText("EventBus : " + currentCount + ",  " + deltaSum);
        } else {
            ((TextView)findViewById(R.id.resultTV)).append("EventBus : " + deltaSum + "\n");
            messageBusTest.setEnabled(true);
            eventBusTest.setEnabled(true);
        }
    }
    private void sendMessageToEventBus() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                EventBusModel model = new EventBusModel();
                model.startTimeNano = System.nanoTime();
                EventBus.getDefault().post(model);
            }
        }, 20);
    }
}


결과 
# Test 1
  - MessageBus :  26,943,562 nano sec   (26.94ms)
             1건당 :         5,388 nano sec    (0.0053ms)
  - EventBus     : 301,392,010 nano sec (301.39ms)
             1건당 :       60,278 nano sec     (0.06ms)

# Test 2
  - MessageBus :    92,949,746 nano sec    (92.95ms)
             1건당 :         18,589 nano sec      (0.019ms)
  - EventBus     : 1,219,814,100 nano sec (1218.81 ms)
             1건당 :       243,962 nano sec       (0.244ms)




위의 결과를 보게되면, 
MessageBus가 EventBus보다 11~13배 빠르다고 볼 수 있습니다. 

현재 MessageBus는 EventBus가 제공하는 모든 기능을 제공하지는 않지만 지속적인 업데이트가 진행될 것이고, 현재 상황에서도 그렇게 복잡한 기능이 꼭 필요한 것이 아니라면 MessageBus를 사용하는 것이 더 나은 선택이 아닐까 합니다. 

P.S. 더 필요한 기능이 있거나, 버그를 발견하신다면 github에 이슈로 올려주시면 감사하겠습니다. 

https://github.com/UnsignedUSB/MessageBus

댓글

댓글 쓰기

이 블로그의 인기 게시물

[Android] DataBinding의 동작방식 - 4. include Tag 혹은 ViewStub 사용시의 Binding

[Android] DataBinding의 동작방식 - 5. Listener, Callback (CustomView의 Callback을 람다식으로 Binding하기)

[Android] DataBinding의 동작방식 - 2. BindingAdapter의 기본 및 사용 시점