Android - Handler 사용 방법

Handler는 안드로이드의 기본적인 이벤트를 처리하는 시스템입니다. Handler를 사용하는 이유는 비동기적(Asynchronous)으로 어떤 Job을 처리하기 위해서 입니다.

Activity의 Main thread는 UI thread라고 불리며, UI를 처리할 때 시간이 오래 걸리는 작업을 수행하면 화면이 버벅이거나 ANR(Android Not Responding)과 같은 문제가 발생합니다.

이 글에서는 다음과 같은 것들을 알아볼 예정입니다.

  1. Handler 사용 방법
  2. Looper를 이용하여, 다른 Thread에서 Handler 사용 방법
  3. Thread 클래스를 이용하여, 다른 Thread에서 Handler 사용 방법

이 글은 Kotlin으로 작성되었습니다. 이 글에서 사용한 예제는 GitHub에서 확인할 수 있습니다.

Handle 기본 사용 방법

먼저 다음과 같이 Handler 클래스를 구현할 수 있습니다. 가장 중요한 것은 handleMessage()를 구현해야 하는 것입니다. 이벤트가 Handler로 전달되면 handleMessage()에서 처리되기 때문입니다.

class MyHandler : Handler() {
    companion object {
        const val TAG = "MyHandler"
        const val MSG_DO_SOMETHING1 = 1
        const val MSG_DO_SOMETHING2 = 2
        const val MSG_DO_SOMETHING3 = 3
        const val MSG_DO_SOMETHING4 = 4
    }
    override fun handleMessage(msg: Message) {
        when (msg.what) {
            MSG_DO_SOMETHING1 -> {
                Log.d(TAG, "Do something1")
            }
            MSG_DO_SOMETHING2 -> {
                Log.d(TAG, "Do something2")
            }
            MSG_DO_SOMETHING3 -> {
                Log.d(TAG, "Do something3")
            }
            MSG_DO_SOMETHING4 -> {
                Log.d(TAG, "Do something4, arg1: ${msg.arg1}," +
                      " arg2: ${msg.arg2}, obj: ${msg.obj}")
            }
        }
    }
}

다음과 같이 Handler를 생성하고 이벤트를 전달할 수 있습니다.

// 1
val handler: Handler = MyHandler()

// 2
val msg: Message = handler.obtainMessage(MyHandler.MSG_DO_SOMETHING1)
handler.handleMessage(msg)

// 3
handler.sendEmptyMessage(MyHandler.MSG_DO_SOMETHING2)

// 4
val msg2 = Message.obtain(handler, MyHandler.MSG_DO_SOMETHING3)
handler.handleMessage(msg2)
  1. Handler를 생성하는 코드입니다.
  2. Message 객체를 만들고, handleMessage()로 이벤트를 전달합니다.
  3. sendEmptyMessage()에 이벤트를 인자로 넣고 전달할 수도 있습니다.
  4. obtain() 메소드를 이용하여 Message 객체를 만들고, 이벤트를 전달할 수 있습니다.

실행하면 다음과 같이 출력이 됩니다.

12-29 19:36:08.469  7438  7438 D MyHandler: Do something1
12-29 19:36:08.469  7438  7438 D MyHandler: Do something2
12-29 19:36:08.469  7438  7438 D MyHandler: Do something3

위의 로그에서 '7438 7438'는 순서대로 ProcessID ThreadID를 의미합니다. PID와 TID가 동일한 것은 Main thread이며, UI thread라고 불립니다.

이벤트 처리 지연

이벤트를 보내는 즉시 처리하지 않고 일정시간 뒤에 처리할 수 있도록 만들 수 있습니다.

// 1
handler.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING1, 5000)
// 2
handler.sendEmptyMessage(MyHandler.MSG_DO_SOMETHING2)
// 3
handler.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING3, 2000)
  1. MSG_DO_SOMETHING1 이벤트를 5000ms 뒤에 처리하게 합니다.
  2. MSG_DO_SOMETHING2를 지연 없이 처리합니다.
  3. MSG_DO_SOMETHING3를 2000ms 뒤에 처리하도록 합니다.

결과를 보시면, 위에서 설정한 간격대로 수행되었습니다.

12-29 18:09:35.743  5149  5149 D MyHandler: Do something2
12-29 18:09:37.743  5149  5149 D MyHandler: Do something3
12-29 18:09:40.744  5149  5149 D MyHandler: Do something1

데이터와 함께 이벤트 전달

위의 예제는 이벤트만 전달하는 것이었습니다. 이벤트를 전달할 때 인자도 함께 전달할 수 있습니다.

val msg = handler.obtainMessage(MyHandler.MSG_DO_SOMETHING4)
msg.arg1 = 10              // 1
msg.arg2 = 100             // 1
msg.obj = Person("chacha") // 2
handler.sendMessage(msg)
  1. arg1, arg2는 int 변수입니다. integer를 전달할 때 사용할 수 있습니다.
  2. obj는 Object 변수이며, 객체 1개를 보낼 수 있습니다.

위 코드의 실행 결과입니다.

12-29 18:18:10.666  5406  5406 D MyHandler: Do something4, arg1: 10, arg2: 100, obj: Person(name=chacha)

다른 Thread에서 Handler 이벤트 처리(Looper)

만약 오래 걸리는 작업이 있다면 UI thread에서 처리하면 안됩니다. 이런 경우 다른 Thread에서 처리되도록 구현해야 합니다.

사실 Handler는 Looper라는 객체를 통해 동작합니다. 1개의 Thread에 1개의 Looper가 있다고 생각하시면 됩니다. 따라서, Handler가 다른 쓰레드에서 동작하게 만드려면, 다른 쓰레드의 Looper로 Handler를 동작하게 만들어야 합니다.

Handler에 인자로 Looper를 전달하지 않으면 실행 중인 Thread의 Looper를 사용합니다. 다음과 같이 Looper를 인자로 받는 Handler를 만들었습니다.

class MyHandlerWithLooper(looper: Looper) : Handler(looper) {
    companion object {
        const val TAG = "MyHandler"
        const val MSG_DO_SOMETHING1 = 1
        const val MSG_DO_SOMETHING2 = 2
        const val MSG_DO_SOMETHING3 = 3
        const val MSG_DO_SOMETHING4 = 4
    }
    override fun handleMessage(msg: Message) {
        when (msg.what) {
            MSG_DO_SOMETHING1 -> {
                Log.d(TAG, "Do something1")
            }
            MSG_DO_SOMETHING2 -> {
                Log.d(TAG, "Do something2")
            }
            MSG_DO_SOMETHING3 -> {
                Log.d(TAG, "Do something3")
            }
            MSG_DO_SOMETHING4 -> {
                Log.d(TAG, "Do something4, arg1: ${msg.arg1}," +
                      " arg2: ${msg.arg2}, obj: ${msg.obj}")
            }
        }
    }
}

위의 클래스는 다음과 같이 생성할 수 있습니다.

// 1
val handlerThread = HandlerThread("MyHandlerThread")
// 2
handlerThread.start()

// 3
val handlerWithLooper = MyHandlerWithLooper(handlerThread.looper)

// 4
Log.d(TAG, "This is UI Thread!")
handlerWithLooper.sendEmptyMessage(MyHandler.MSG_DO_SOMETHING1)
handlerWithLooper.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING2, 1000)
handlerWithLooper.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING3, 2000)
  1. HandlerThread는 Handler에 사용할 목적으로 만드는 Thread입니다.
  2. start()를 호출해야 Thread가 동작합니다.
  3. Handler에 인자로 Looper를 전달하여 생성합니다.
  4. handler로 이벤트를 전달합니다.

실행 결과입니다. TID(Thread ID)를 보시면 UI thread가 아닌 것을 알 수 있습니다.

12-29 18:26:16.613  5800  5800 D MainActivity: This is UI Thread!
12-29 18:26:16.613  5800  5829 D MyHandler: Do something1
12-29 18:26:17.613  5800  5829 D MyHandler: Do something2
12-29 18:26:18.614  5800  5829 D MyHandler: Do something3

다른 Thread에서 Handler 이벤트 처리(Thread)

Thread 클래스를 구현하고, 그 Thread의 Looper로 Handler를 만들 수 있습니다.

다음 코드는 클래스 정의와 클래스를 생성하여 이벤트를 전달하는 코드입니다.

class MyThreadForHandler : Thread() {
    private var myHandler: MyHandler? = null
    override fun run() {
        Looper.prepare()          // 1
        myHandler = MyHandler()   // 2
        Looper.loop()             // 3
    }

    fun doSomething() {
        myHandler!!.sendEmptyMessage(MyHandler.MSG_DO_SOMETHING1)
        myHandler!!.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING2, 1000)
        myHandler!!.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING3, 2000)
    }
}


// 4
val myThreadForHandler = MyThreadForHandler()
myThreadForHandler.start()

// 5
Log.d(TAG, "This is UI Thread!")
myThreadForHandler.doSomething()
  1. Looper.prepare()는 Looper를 생성하고 Looper가 이벤트를 처리할 수 있는 상태로 만듭니다.
  2. Looper 초기화 이후에 Handler를 생성하면 실행 중인 Thread의 Looper가 Handler에 설정됩니다.
  3. Looper.loop()는 Looper를 실행시키는 API입니다. 이제 이벤트가 오면 Looper가 처리하게 됩니다.
  4. 구현한 Thread 객체를 만듭니다. start()를 호출하면 클래스의 run()메소드가 callback됩니다.
  5. 클래스의 함수를 호출하여 Handler로 이벤트를 전달하게 합니다.

실행 결과입니다. TID를 보시면 Handler는 UI thread와 다른 Thread에서 동작하였습니다.

12-29 18:38:42.997  6973  6973 D MainActivity: This is UI Thread!
12-29 18:38:42.998  6973  6999 D MyHandler: Do something1
12-29 18:38:43.999  6973  6999 D MyHandler: Do something2
12-29 18:38:44.998  6973  6999 D MyHandler: Do something3

이 글에서 사용한 예제는 GitHub에서 확인할 수 있습니다.

참고

Loading script...

Related Posts

codechachaCopyright ©2019 codechacha