본문 바로가기
안드로이드/코틀린

(android)greenrobot - EventBus

by 나이아카 2021. 4. 6.

 이 글의 주제는 그린로봇이 만든 이벤트 버스라는 라이브러리입니다. 안드로이드에서 사용되는 이벤트 버스는 가장 유명한 것으로 이 greenrobot과 otto가 있는데 저는 이 그린로봇의 이벤트 버스를 사용중이기 때문에 이에 대해 다루게 되었습니다.

 

 

그린 로봇의 이벤트 버스 설명

 

 위 그림은 이벤트 버스의 동작 방식을 간단하게 도형으로 설명한 부분입니다.(이 그림은 greenrobot의 github readMe에 나와있는 그림입니다.)

 

 소개할 라이브러리를 보기 전에 이벤트 버스의 개념에 대해서 한 번 살펴보겠습니다.

 

 이벤트 버스버스 네트워크의 동작 방식과 유사하게 하나의 이벤트를 동일 채널에 연결된 모든 노드(Subscriber)들에게 보내는 방식을 의미합니다. 이를 통해 각 노드들은 채널에 연결되어 있기만 하다면 이벤트를 채널에 보내는 얕은 결합으로도 간단하게 데이터를 전달받을 수 있게 됩니다. 안드로이드의 경우 액티비티와 액티비티의 통신은 intent를 통해, fragment와 activity의 경우는 bundle이나 콜백을 이용해서 제작할 수 있습니다. 그러나 이는 두 노드 간의 결합도를 높여 재사용성을 줄이고 코드를 복잡하게 만드는 단점을 지니고 있습니다.(물론 여러가지 디자인 패턴을 적용하고 라이브러리를 이용한다면 충분히 다양한 방법으로 해결할 수 있습니다. 이벤트 버스 역시 그 중 하나입니다.)

 

 이러한 결합도를 낮추면서 각 UI끼리의 데이터 통신을 좀 더 원활하게 하고자 하는 것이 이벤트 버스의 개념입니다. 물론 이는 하나의 이벤트를 모든 노드들이 받기 때문에 메모리 사용이 증가할 수 있고, 각 노드들이 스스로 필터링을 해주어야 한다는 문제도 있습니다. 또한 데이터를 받지 못하는 경우에 대해서도 보장해주지 않습니다.

 

 모든 방식에는 장단점이 존재하기 때문에 사용을 하기 전에는 충분히 적용할 프로젝트에 알맞은 라이브러리인지에 대한 확인은 필수지만, 여러가지 프로젝트에서 이 이벤트 버스 라이브러리의 장점 덕분에 차용하고 있습니다.

 

 이 글을 보기보단 직접 깃허브에서 보고 적용하고 싶으신 분들을 위해 깃허브 링크를 걸어두겠습니다.

github.com/greenrobot/EventBus

 

greenrobot/EventBus

Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality. - greenrobot/EventBus

github.com


implementation 'org.greenrobot:eventbus:3.2.0'

 

 항상 라이브러리를 사용할 때 제일 먼저 해야하는 일입니다! 이벤트 버스 라이브러리를 사용하기 위해서는 안드로이드 build.gradle에서 dependencies 내부에 위와 같은 코드를 통해 적용해야 합니다. 

 

 gradle을 통해 프로젝트에 이벤트버스를 사용할 수 있게 되었다면 이제는 activity 및 fragment에 이벤트 버스를 등록할 차례입니다.

 

override fun onResume() {
    super.onResume()
    if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this)
}

override fun onPause() {
    super.onPause()
    if (EventBus.getDefault().isRegistered(this)) EventBus.getDefault().unregister(this)
}

 

 이벤트 버스를 등록하기 위해서는 위와 같은 코드를 넣어주어야 합니다. 여기서 this는 프래그먼트 및 액티비티를 의미합니다. 둘 다 subscriber가 될 수 있습니다. 저는 onResume과 onPause에서 이벤트버스를 등록하고 해제하고 있지만, 이벤트 버스 공식 문서에서는 onStart와 onStop에서 등록 및 해제를 권하고 있으니 이는 실제 프로젝트에서 사용할 때 문제가 발생하지 않는지 잘 판단해서 원하는 부분에 작성하시면 됩니다.

 

 프래그먼트및 액티비티의 생명주기에 보면 onStart가 onResume보다 먼저 오고 onStop이 onPause보다 늦게 옵니다. 이를 잘 생각하셔서 사용해야 할 겁니다!

 

 이제 이벤트 버스를 등록했다면 실제로 사용하는 일만 남았습니다.

 

class CallEvent(val id: Int)

 

 이벤트에서 사용하기 위한 클래스를 작성합니다. 여기서는 추후 이벤트를 받을 메소드 내부에 전달하고자 하는 데이터를 파라미터로 만들어 넘길 수 있습니다. 제 예제에서는 id라는 변수 하나를 넘기고 있습니다.

 

@Subscribe(threadMode = ThreadMode.MAIN)
fun printId(event: CallEvent) {
    Toast.makeText(context, "${event.id}", Toast.LENGTH_SHORT).show()
}

 

 이 메소드는 간단하게 id를 받아 토스트로 출력하는 기능을 합니다. 매우 간단해서 이 코드를 보고 더 어려운 코드를 생성하기 어려울 수도 있을까 싶지만, 이 정도 예제로도 충분할만큼 이 라이브러리는 간단합니다. 이제 저 메소드 내부에서 이벤트를 받은 UI가 원하는 작업을, 이벤트로 넘길 class에서 넘기고자 하는 데이터를 파라미터로 가지면 됩니다.

 

이 메소드에서 받은 CallEvent는 이벤트버스를 받기 위한 필터라고 보시면 되겠습니다. CallEvent를 채널에 전달되는 경우 이 메소드가 그 이벤트를 받겠다고 지정하는 것입니다. 만약 CallEvent가 아니라 다른 이벤트 클래스가 들어올 경우 

필터링을 통해 실행되지 않도록 말이죠.

 

EventBus.getDefault().post(CallEvent(id))

 

 이벤트를 발생시키고 싶은 코드 부분에서 EventBus.getDefault().post를 통해 이벤트를 발생시킬 수 있습니다. 이때 post 안에 들어가는 class를 이용해 어떤 이벤트를 발생시킬 것인지를 정하게 됩니다.

 

저 세 부분이 이벤트 버스 깃허브 공식 문서에서 설명하고 있는 부분입니다.

 

class MainFragment : Fragment()
{
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //코드
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        binding.lifecycleOwner = this
        return binding.root
    }
    
    override fun onResume() {
    	super.onResume()
    	if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this)
    }

    override fun onPause() {
        super.onPause()
        if (EventBus.getDefault().isRegistered(this)) EventBus.getDefault().unregister(this)
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    fun printId(event: CallEvent) {
        Toast.makeText(context, "${event.id}", Toast.LENGTH_SHORT).show()
    }
    
}

 

 이벤트 버스를 받을 메인 프래그먼트의 가장 기본적인 구성입니다. onResume(onStart), onPause(onStop)에 이벤트 버스를 등록하고 해제해 이벤트를 받을 수 있도록 하고, @Subscribe를 통해 데이터를 불러옵니다.

 이때 쓰레드의 모드는 ThreadMode.MAIN으로 메인 쓰레드에서 이벤트를 처리한다는 것을 의미합니다. (다른 모드를 사용할 경우 백그라운드에서도 동작하게 만들 수 있습니다.)

 

 저런 프래그먼트를 생성했다면 이제 위에서 설명한 post를 이용해 이벤트를 던지고, 저 프래그먼트에서 받을 수 있도록 처리하면 됩니다. 하지만 저 때 제일 크게 주의해야 할 점은 저 프래그먼트가 현재 보이고 있는 (lifecycle이 onResume과 onPause 사이에 있는게 맞는지) 상태인지 확인해야 합니다. 프래그먼트가 백그라운드에 숨어 있거나 존재하지 않는, 파괴된 프래그먼트라면 이벤트는 제 위치를 찾지 못하고 익셉션을 발생시키게 됩니다.

 

 

익센셥 관련 내용

 

Caused by: org.greenrobot.eventbus.EventBusException: Subscriber class MainFragment and its super classes have no public methods with the @Subscribe annotation

 

@Subscribe 주석이 없고 이벤트 버스만 등록하는 경우, 프래그먼트가 생성될 때, 이벤트가 없다는 익셉션을 발생 시킵니다. 이는 쓰지 않는 곳에서 이벤트버스를 등록하고 해제하는 관리를 하지 말라는 의미로 발생시키는 것으로 보입니다.

 

 

No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent

 

 이 부분은 익셉션이라기보다는(애플리케이션이 터지지 않습니다!) 이벤트 버스가 왜 실행되지 않았는지에 대해 라이브러리에서 직접 정의해놓은 부분이라고 볼 수 있습니다. 이는 이벤트가 등록된 클래스에서 이벤트가 발생한 시점에서 이벤트버스가 등록되어 있지 않아서 찾을 수 없다고 알려주는 부분입니다. 만약 이벤트가 발생하지 않고 이런 로그를 발견한다면 라이프사이클이 제대로 되어 있는지, 혹은 아예 이벤트 자체를 걸어두지 않은게 아닌지 확인해야 합니다.

 


 

 이벤트 버스는 의외로 안드로이드 프로젝트를 하면서 자주 쓰입니다. 아무래도 여러가지 방법들이 있지만 한 번 사용하게 되면 쉽게 사용이 가능하다는 장점 덕분에 다른 방법보다는 자주 애용하는 것 같습니다.(물론 이건 제 개인적인 의견입니다.)

 

 그래서 혹여나 나중에 다시 사용하게 될 때를 대비해 조금 정리해두고자 글을 작성하였습니다.

댓글