Handler는 안드로이드의 기본적인 이벤트를 처리하는 시스템입니다. Handler를 사용하는 이유는 비동기적(Asynchronous)으로 어떤 Job을 처리하기 위해서 입니다.
Activity의 Main thread는 UI thread라고 불리며, UI를 처리할 때 시간이 오래 걸리는 작업을 수행하면 화면이 버벅이거나 ANR(Android Not Responding)과 같은 문제가 발생합니다.
이 글에서는 다음과 같은 것들을 알아볼 예정입니다.
- Handler 사용 방법
- Looper를 이용하여, 다른 Thread에서 Handler 사용 방법
- 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)
- Handler를 생성하는 코드입니다.
- Message 객체를 만들고,
handleMessage()
로 이벤트를 전달합니다. sendEmptyMessage()
에 이벤트를 인자로 넣고 전달할 수도 있습니다.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)
MSG_DO_SOMETHING1
이벤트를 5000ms 뒤에 처리하게 합니다.MSG_DO_SOMETHING2
를 지연 없이 처리합니다.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)
- arg1, arg2는
int
변수입니다. integer를 전달할 때 사용할 수 있습니다. - 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)
- HandlerThread는 Handler에 사용할 목적으로 만드는 Thread입니다.
start()
를 호출해야 Thread가 동작합니다.- Handler에 인자로 Looper를 전달하여 생성합니다.
- 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()
Looper.prepare()
는 Looper를 생성하고 Looper가 이벤트를 처리할 수 있는 상태로 만듭니다.- Looper 초기화 이후에 Handler를 생성하면 실행 중인 Thread의 Looper가 Handler에 설정됩니다.
Looper.loop()
는 Looper를 실행시키는 API입니다. 이제 이벤트가 오면 Looper가 처리하게 됩니다.- 구현한 Thread 객체를 만듭니다.
start()
를 호출하면 클래스의run()
메소드가 callback됩니다. - 클래스의 함수를 호출하여 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에서 확인할 수 있습니다.
참고
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력