Handler는 Android에서 어떤 작업을 처리하기 위해 Message를 보내거나 Runnable을 실행하도록 설계된 클래스입니다.
Executor는 Java에서 제공하는 인터페이스로, Runnable을 실행하도록 설계되었습니다.
이 둘은 Runnable과 같은 Task를 특정 Thread에서 수행시키는 공통점이 있습니다. 반면에 여러가지 차이점들도 있습니다.
이 글에서는 Handler와 Executor의 차이점에 대해서 알아보았습니다.
Android 예제 코드는 코틀린으로 작성되었으며, Framework 또는 Java 라이브러리는 Java로 참조하였습니다.
Android Library, Java Library
Handler는 Android에서 제공하는 라이브러리입니다. 반면에 Executor는 Java에서 제공하는 라이브러리입니다. Executor를 사용하면 Android 개발 경험이 없는 Java 개발자가 더 이해하기 쉬울 수 있습니다.
다음은 Android Handler 클래스입니다.
일반적으로 sendMessage()
와 post()
로 Task를 실행시킵니다.
public class Handler {
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
....
}
다음은 Java의 Executor 인터페이스입니다. execute()
로 Task를 실행시킵니다.
public interface Executor {
void execute(Runnable command);
}
Message 전달, Runnable 수행
Handler는 Message 전달 및 Runnable을 수행할 수 있습니다. 반면에 Executor는 Message 전달 기능이 없고, Runnable만 실행시킬 수 있습니다.
Handler는 다음과 같이 handleMessage()
를 구현할 수 있습니다.
class MyHandler : Handler() {
companion object {
const val TAG = "MyHandler"
const val MSG_DO_SOMETHING1 = 1
const val MSG_DO_SOMETHING2 = 2
}
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_DO_SOMETHING1 -> {
Log.d(TAG, "Do something1")
}
MSG_DO_SOMETHING2 -> {
Log.d(TAG, "Do something2")
}
}
}
}
다음과 같이 Message를 보내 Handler의 Thread에서 바로 수행되거나 일정 시간 뒤에 수행되도록 할 수 있습니다.
handler.sendEmptyMessage(MyHandler.MSG_DO_SOMETHING2)
handler.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING3, 2000)
또한, Handler는 다음과 같이 Runnable을 실행할 수 있습니다.
handler.post(object: Runnable {
override fun run() {
// do something
}
})
반면에 Executor는 다음과 같이 Runnable을 실행시키는 것만 할 수 있습니다.
val executor = this.getMainExecutor()
executor.execute(object: Runnable {
override fun run() {
// do something
}
})
참고로, 익명 클래스를 전달하는 대신에 Lambda 또는 Kotlin에서 제공하는 문법으로 다음과 같이 간단히 구현할 수 있습니다.
executor.execute(Runnable {
// do something
});
handler.post {
// do something
}
P OS 부터 Context.getMainExecutor()로 Main Thread에서 동작하는 Executor를 가져올 수 있습니다.
Single Thread, Multi Thread
Handler는 Single Thread에서 Task가 처리됩니다.
하지만 Executor는 클래스에 따라서 Single Thread에서 처리될 수도 있고, Multi Thread에서 처리될 수도 있습니다.
예를 들면, Android에서 다음과 같이 ThreadPoolExecutor
객체를 생성할 수 있습니다. 이 객체는 원하는 크기의 ThreadPool을 생성하고, execute()
로 전달되는 Runnable을 ThreadPool에서 실행시킵니다. 즉, Multi Thread에서 Runnable이 실행됩니다.
val workQueue = LinkedBlockingQueue<Runnable>()
val executor = ThreadPoolExecutor(5 /* corePoolSize */,
5, 50, TimeUnit.MILLISECONDS, workQueue)
executor.execute { Log.d("Test", "Adding task1") }
executor.execute { Log.d("Test", "Adding task2") }
executor.execute { Log.d("Test", "Adding task3") }
executor.execute { Log.d("Test", "Adding task4") }
executor.execute { Log.d("Test", "Adding task5") }
다음은 위 코드의 실행 결과입니다. TID를 보면 모두 다른 Thread에서 수행된 것을 볼 수 있습니다. Single thread가 아니기 때문에 실행 순서 또한 보장되지 않습니다.
12-06 21:41:16.739 5358 6371 D Test : Adding task1
12-06 21:41:16.739 5358 6373 D Test : Adding task3
12-06 21:41:16.739 5358 6375 D Test : Adding task5
12-06 21:41:16.740 5358 6372 D Test : Adding task2
12-06 21:41:16.742 5358 6374 D Test : Adding task4
등록된 Task 개수
Handler의 경우 등록된 Task가 몇개인지 알 수 없습니다.
반면에, 일부 Executor 클래스는 Task 개수에 대한 API를 제공하기도 합니다.
예를 들면, ThreadPoolExecutor
는 getTaskCount()
라는 API로 스케줄링된 Task 개수를 리턴합니다.
val workQueue = LinkedBlockingQueue<Runnable>()
val executor = ThreadPoolExecutor(5, 5,
50, TimeUnit.MILLISECONDS, workQueue)
executor.execute { Log.d("Test", "Adding task1") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task2") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task3") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task4") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task5") }
Log.d("Test", "Task count: ${executor.taskCount}")
Output:
12-06 22:07:10.994 6063 6063 D Test : Task count: 1
12-06 22:07:10.994 6063 6063 D Test : Task count: 2
12-06 22:07:10.995 6063 6063 D Test : Task count: 3
12-06 22:07:10.995 6063 6063 D Test : Task count: 4
12-06 22:07:10.996 6063 6063 D Test : Task count: 5
12-06 22:07:11.040 6063 8556 D Test : Adding task2
12-06 22:07:11.040 6063 8555 D Test : Adding task1
12-06 22:07:11.041 6063 8558 D Test : Adding task4
12-06 22:07:11.041 6063 8557 D Test : Adding task3
12-06 22:07:11.041 6063 8559 D Test : Adding task5
정리
만약 자신이 구현하려는 것이 Executor만으로 충분하다면, Java 라이브러리인 Executor를 사용하는 것이 좋을 것 같습니다. 또한, Executor를 사용하면 현재는 Single Thread에서 Task가 수행되더라도, 나중에 ThreadPool로 쉽게 확장할 수 있습니다.
반면에 Handler를 사용해야할 때가 있습니다. handleMessage()
와 같은 패턴을 사용해야하거나, Context.registerReceiver()
와 같이 인자로 Handler를 요구하는 Android API를 사용할 때 입니다.
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 명령어로 로그 출력