앞선 글에서 프로세스, 스레드, UI 스레드, 그리고 Handler와 Looper까지 살펴봤다.
사실 앞선 글과 이번 글은 안드로이드 프로그래밍 Next Step 3장을 읽다가 정리한 내용을 바탕으로 작성하였다.
앞선 글이 추가적으로 조사한 내용을 바탕으로 작성한 글이고
이 글이 3장의 내용을 정리한 것이다.
3장에서는 백그라운드 스레드를 주제로 다음 두 방식을 소개한다.
둘 다 “백그라운드 작업을 안전하게 처리하기 위한 시도”라는 공통점을 가진다.
HandlerThread는 이름 그대로 Thread를 상속받은 클래스다.
차이점은 단 하나, Looper가 자동으로 붙어 있다는 것이다.
앞에서 봤듯이,
그래서 일반 Thread 안에서 Handler() 기본 생성자를 쓰면 문제가 생긴다.
Handler는 반드시 Looper가 있는 스레드에 붙어야 하기 때문이다.
이 문제를 해결하기 위해 등장한 것이 HandlerThread다.
일반 Thread의 특징은 단순하다.
하지만 이런 요구가 생긴다.
이건 사실상 UI 스레드와 동일한 구조다.
단지 UI가 없을 뿐이다.
그래서 안드로이드는
를 가진 백그라운드 전용 스레드를 제공했고,
그게 HandlerThread다.
여기서 중요한 문장이 하나 있다.
스레드는 기본적으로 실행 순서를 보장하지 않는다.
예를 들어 즐겨찾기 버튼을 빠르게 클릭한다고 해보자.
이 상태에서 스레드들이 서로 순서를 보장하지 않으면,
이게 바로 레이스 컨디션이다.
그래서 필요한 게 큐(queue)다.
HandlerThread는 이 구조를 자연스럽게 제공한다.
하지만 HandlerThread는 여전히 단점이 많다.
Looper.loop()는 무한 루프라 직접 종료해야 함Looper.quit()는 다른 스레드에서 호출해야 함그래서 다음 단계로 등장한 것이 AsyncTask다.
AsyncTask는 Thread보다 안드로이드 개발자에게 친숙했다.
이유는 간단하다.
즉, “스레드 + Handler + UI 전환”을 한 번에 감싸준 도구였다.
가장 큰 문제는 액티비티 생명주기를 따라가지 않는다는 점이다.
이 문제를 막기 위해 isCancelled()를 체크하고
onDestroy()에서 cancel을 호출하는 방식이 등장했지만,
이건 개발자에게 책임을 떠넘긴 설계에 가깝다.
AsyncTask에서 예외 처리는 까다롭다.
이 문제를 해결하기 위해 RxJava가 주목받기 시작했다.
RxJava는
onNextonErroronComplete처럼 에러를 구조적으로 다룰 수 있었기 때문이다.
AsyncTask는 병렬 실행이 가능하다.
하지만 병렬 실행은 항상 위험을 동반한다.
예를 들어,
이 두 개가 순서대로 실행되어야 한다면?
병렬 실행에서는
이건 운의 영역이다.
그래서 이런 경우에는 차라리
실행 순서를 조정하기 위해
CountDownLatch 같은 동기화 도구가 등장했지만,
코드는 점점 복잡해졌다.
여기까지의 흐름을 정리하면 명확하다.
그래서 안드로이드는 결국 Coroutine을 표준으로 선택했다.
Coroutine은
viewModelScope.launch {
val overview = loadOverview()
val detail = loadDetail(overview.id)
updateUi(detail)
}
이 코드는
HandlerThread와 AsyncTask는
안드로이드가 비동기 문제를 해결하기 위해 지나온 과정이다.
지금 우리가 Coroutine을 쓰는 이유는 단순히 “새로워서”가 아니다.
이 모든 문제를 현실적으로 가장 잘 해결한 도구이기 때문이다.