안드로이드 앱이 처음 시작되면 런타임 시스템은 하나의 스레드를 생성하며, 모든 앱 컴포넌트는 기본적으로 이 스레드 내에서 실행됩니다. 즉, 안드로이드 4대 컴포넌트(Activity, Service, Content Provider, Broadcast Receiver)가 생성될 때 별도의 스레드가 생성되는 것이 아니라 이 하나의 스레드에서 실행됩니다. 이 스레드를 메인 스레드(Main Thread)라고 부릅니다.
시스템에 의해 생성된 이 Main thread의 주된 역할은 사용자 인터페이스를 처리하는 것입니다. 예를 들어 TextView, Button 등의 View를 생성하고 사용자가 이들을 조작하였을 때 상호작용하는 스레드가 Main Thread입니다. 그래서 Main Thread를 UI Thread 라고도 합니다.
그런데 이런 중요한 Main Thread를 사용해서 시간이 오래 걸리는 작업을 수행하면 해당 작업이 끝날 때까지 앱 전체가 멈춘 것처럼 보이게 됩니다. 예를 들어 개발자가 동영상을 다운로드하는 작업을 Main Thread(UI Thread)에 구현하였다면, 사용자가 동영상 다운로드 버튼을 클릭했을 때 다운로드가 완료될 때까지 화면은 멈추어있는 것처럼 보일 것입니다. 이런 경우 안드로이드에서는 '앱이 응답하지 않음(ANR = Application Not Responding)' 과 같은 경고를 보여주게 됩니다.
이런 상황은 앱의 반응성을 떨어지게 만들기에 사용자에게 답답함을 주고 좋지 않은 앱으로 낙인됩니다. 따라서 개발자는 반응성이 좋은 앱을 개발하기 위해서 시간이 오래 걸리는 작업은 별도의 스레드에서 수행하도록 하여 Main Thread가 방해를 받지 않고 다른 작업을 계속하게 해주어야 합니다.
따라서 안드로이드 앱의 개발의 핵심 규칙 중 하나는 앱의 메인 스레드에서 시간이 오래 걸리는 작업을 절대로 수행하지 않는 것 입니다. 그리고 UI 툴킷이 스레드에 안전하지 않기 때문에(여러 스레드가 동시에 실행되면서 각종 리소스를 공유할 때 생길 수 있는 많은 문제에 대한 대비를 하지 못한다는 의미) 메인 스레드 외의 다른 스레드 코드에서는 어떤 상황에서든 UI를 직접 변경해서는 안 되며, UI의 변경은 항상 Main Thread(UI Thread)에서만 수행되어야 합니다.
Thread-safe(스레드에 안전하다)는 것은 여러 스레드가 동시에 하나의 객체 및 변수(공유 자원)에 접근 할 때, 의도한 대로 동작하는 것을 말합니다. 이와는 반대로 스레드에 안전하지 않다는 여러 스레드가 동시에 하나의 객체 및 변수에 접근할 때, 의도한 대로 동작하지 않고 많은 문제가 발생할 수 있다는 것입니다. 자세한 내용은 스레드 안전 - 위키백과 에 나와있습니다.
ANR은 아래와 같은 두 가지의 경우에 나타날 수 있습니다.
액티비티가 foreground에서 동작하는 동안(현재 앱의 액티비티가 화면에 보임), 앱이 입력 이벤트 또는 Broadcase Receiver(키를 누르거나 화면 터치하는 이벤트)에 5초 이내에 반응하지 않는다면 발생
액티비티가 foreground에 없는 동안에(현재 앱의 액티비티가 화면에 보이지 않음) BroadcaseReceiver가 주어진 시간 내에 실행을 완료하지 못했을 때 발생
가장 대표적으로 액티비티에서 ANR 문제를 발생시키는 부분이 네트워킹 입니다.
서버 연동을 위한 네트워킹은 HTTP 프로토콜을 이용하거나 TCP/IP 프로토콜을 이용하는데, 서버 요청부터 응답까지 대부분 1 ~ 2초 이내에 처리되므로 5초까지 시간이 걸리지 않는다고 생각하기 쉽습니다. 유선 네트워킹에서는 신뢰할 수 있는 네트워크 연결을 이용하므로 빠르게 처리될 수 있지만, 모바일처럼 무선 네트워크를 사용하는 경우 예외 상황이 굉장히 많습니다. 예를 들어 엘리베이터를 타거나 이동통신망에서 와이파이망으로 갈아타는 등의 상황에서는 시간이 오래 걸릴 수 있습니다.
시간이 오래 걸리는 작업을 Main Thread에서 분리하고 별도의 스레드에서 실행하는 것이 ANR 문제를 해결하는 방법입니다. 즉, 수행흐름을 하나가 아닌 다른 수행 흐름을 만들어 시간이 오래 걸리는 작업을 처리하면 되는 것입니다. 이는 스레드를 하나 생성하여 처리하도록 하면 해결할 수 있습니다.
이외에도 AsyncTask, RxJava, RxAndroid, 코틀린의 Coroutine을 통해서 해결할 수 있는데 이와 관련된 글은 다른 글로 작성하도록 하겠습니다.
AsyncTask
참고로 안드로이드의 AsyncTask는 안드로이드 11(API 레벨 30)부터 Deprecated 되었습니다. 이 클래스는 백그라운드 작업과 UI 작업 간의 상호작용을 단순화해 주는 편리한 비동기 작업 도구였습니다. 그러나 다른 컴포넌트의 생명주기와 연동되지 않아 많은 문제가 발생할 수 있었습니다. 따라서 실무에서는 RxJava나 코틀린의 Coroutine과 같은 라이브러리를 많이 사용한다고 합니다.
시간이 오래 걸리는 작업은 Main Thread(UI 스레드)로부터 분리하고 별도의 스레드에서 실행함으로써 UI 스레드 작업이 지연(또는 차단)되는 것을 방지합니다.
UI 스레드가 아닌 스레드에서는 UI 구성 요소를 조작하지 않습니다. 따라서 개발자가 생성한 스레드에서는 UI 구성 요소를 조작하기 위해서 Handler와 Looper와 MessageQueue를 활용해야 하는데, 이는 Looper에서 참고할 수 있습니다.
참고
깡쌤의 안드로이드 프로그래밍
안드로이드 메인 스레드(Android Main Thread 또는 UI Thread)
틀린 부분은 댓글로 남겨주시면 수정하겠습니다..!!