안드로이드에서 프로세스와 스레드, 그리고 Looper·Handler·Coroutine까지

유진·2025년 12월 13일

Android

목록 보기
13/17
post-thumbnail

https://developer.android.com/guide/components/processes-and-threads

안드로이드 개발을 하다 보면 자연스럽게 이런 질문을 만나게 된다.

  • 왜 UI 스레드를 막으면 안 될까?
  • Handler와 Looper는 정확히 뭘 하는 걸까?
  • Coroutine은 왜 더 안전하고 편한 방식일까?

이 글에서는 운영체제 관점안드로이드 내부 구조를 함께 엮어, 안드로이드의 실행 모델을 처음부터 끝까지 정리해본다.


1. 프로세스(Process): 앱의 실행 단위

운영체제에서 프로세스는 실행 중인 프로그램의 인스턴스다.
각 프로세스는 독립된 메모리 공간을 가지며, 다른 프로세스와 메모리를 직접 공유하지 않는다.

안드로이드에서도 동일하다.

  • 앱이 실행되면 리눅스 프로세스 1개가 생성된다.
  • 기본적으로 Activity, Service, BroadcastReceiver, ContentProvider는
    모두 같은 프로세스에서 실행된다.

즉, 일반적인 안드로이드 앱은 “앱 하나 = 프로세스 하나” 구조를 가진다.

필요하다면 android:process 속성을 통해 컴포넌트를 분리할 수 있지만, 대부분의 앱에서는 사용하지 않는다.


2. 안드로이드 프로세스는 언제 종료될까

안드로이드는 메모리가 부족해지면 프로세스를 종료한다.
이때 기준은 “사용자에게 얼마나 중요한가”다.

  • 화면에 보이는 Activity를 가진 프로세스는 우선순위가 높다.
  • 백그라운드에만 있는 프로세스는 언제든 종료될 수 있다.

그래서 안드로이드 앱은 항상 프로세스가 죽고 다시 살아나는 상황을 전제로 설계해야 한다.


3. 스레드(Thread): 실행 흐름의 단위

프로세스 안에는 하나 이상의 스레드가 존재한다.

  • 스레드는 프로세스의 메모리를 공유한다.
  • 대신 동시성 문제(레이스 컨디션)가 발생할 수 있다.

안드로이드 앱이 시작되면 가장 먼저 생성되는 스레드가 있다.


4. 메인 스레드(UI 스레드)의 역할

안드로이드는 앱 시작 시 메인 스레드(Main Thread)를 하나 생성한다.
이 스레드는 흔히 UI 스레드라고도 불린다.

UI 스레드의 역할은 다음과 같다.

  • 터치, 키 입력 같은 사용자 이벤트 처리
  • 화면 그리기
  • Activity / Fragment 생명주기 콜백 실행

즉, UI 스레드는 앱의 화면과 상호작용을 전담하는 핵심 스레드다.


5. 왜 UI 스레드를 막으면 안 되는가

UI 스레드는 이벤트를 하나씩 처리한다.

  1. 버튼 클릭 이벤트 처리
  2. 화면 상태 변경
  3. 다시 그리기 요청 처리

이 과정 중 UI 스레드에서 오래 걸리는 작업을 수행하면 어떻게 될까?

  • 이벤트가 처리되지 않는다.
  • 화면이 갱신되지 않는다.
  • 사용자는 앱이 멈췄다고 느낀다.

5초 이상 UI 스레드가 응답하지 않으면
안드로이드는 ANR(Application Not Responding)을 발생시킨다.

그래서 안드로이드에는 명확한 규칙이 있다.

  1. UI 스레드를 블로킹하지 말 것
  2. UI 변경은 반드시 UI 스레드에서만 할 것

6. 이벤트 루프와 Looper

여기서 자연스럽게 질문이 생긴다.

UI 스레드는 어떻게 앱이 종료될 때까지 계속 살아 있을까?

그 답이 이벤트 루프(Event Loop)다.

UI 스레드는 내부적으로 다음과 같은 구조를 가진다.

메시지가 올 때까지 대기
→ 메시지 하나 꺼내서 실행
→ 다시 대기

이 무한 루프를 담당하는 객체가 Looper다.

  • Looper는 스레드에 하나만 존재한다.
  • UI 스레드는 기본적으로 Looper를 가지고 있다.
  • 일반 스레드는 Looper가 없다.

7. MessageQueue와 Handler

Looper 옆에는 항상 MessageQueue가 있다.

  • MessageQueue에는 실행할 작업(Runnable, Message)이 쌓인다.
  • Looper는 이 큐를 계속 감시하며 하나씩 실행한다.

그렇다면 누가 큐에 작업을 넣을까?

그 역할을 하는 것이 Handler다.

Handler는 특정 Looper(즉, 특정 스레드)에 연결되어
그 스레드의 MessageQueue에 작업을 전달한다.

Handler(Looper.getMainLooper()).post {
    textView.text = "hello"
}

이 코드는 “UI 스레드에서 이 코드를 실행해달라”는 요청이다.


8. 백그라운드 스레드와 UI 스레드 통신

네트워크, DB, 파일 IO 같은 작업은
반드시 UI 스레드가 아닌 백그라운드 스레드에서 수행해야 한다.

하지만 UI 업데이트는 UI 스레드에서만 가능하다.

그래서 일반적인 흐름은 다음과 같다.

  1. 백그라운드 스레드에서 작업 수행
  2. Handler를 통해 UI 스레드에 결과 전달
  3. UI 스레드 Looper가 이를 실행

이 구조 덕분에 UI 스레드는 안전하게 유지된다.


9. HandlerThread: Looper를 가진 백그라운드 스레드

일반 Thread는 작업이 끝나면 종료된다.
하지만 계속 살아 있으면서 요청을 처리해야 하는 스레드도 필요하다.

이를 위해 제공되는 것이 HandlerThread다.

  • Thread + Looper를 합친 구조
  • 순차 처리 보장
  • 시스템 컴포넌트나 레거시 코드에서 자주 사용

다만 코드 복잡도가 높고 생명주기 관리가 어렵다.


10. Coroutine: 더 높은 수준의 추상화

Coroutine은 Thread를 직접 다루지 않는다.
이미 존재하는 스레드 위에서 실행 흐름만 관리한다.

핵심은 Dispatcher다.

  • Dispatchers.Main → UI 스레드
  • Dispatchers.IO → IO 작업용 스레드 풀
withContext(Dispatchers.Main) {
    textView.text = "hello"
}

은 내부적으로 다음과 같다.

Handler(Looper.getMainLooper()).post {
    textView.text = "hello"
}

이처럼, Coroutine은 Handler + Looper 구조 위에서 동작하지만, 개발자는 이를 직접 신경 쓰지 않아도 된다.


11. 왜 Dispatchers.Main은 안전한가

Dispatchers.Main은 내부적으로

  • UI 스레드의 Looper
  • UI 스레드의 MessageQueue

에 작업을 등록한다.

즉, Handler를 직접 쓰는 것과 본질적으로 동일하지만
더 안전하고 가독성이 좋으며 취소와 생명주기 관리가 가능하다.


12. 정리

안드로이드의 실행 구조를 한 줄로 요약하면 다음과 같다.

  • 프로세스는 앱의 실행 단위다.
  • 스레드는 실행 흐름의 단위다.
  • UI 스레드는 이벤트와 화면을 담당한다.
  • Looper는 스레드의 이벤트 루프다.
  • Handler는 특정 스레드에 작업을 전달하는 도구다.
  • Coroutine은 이 모든 구조 위에 얹힌 고수준 추상화다.

이 흐름을 이해하면
왜 안드로이드가 이런 구조를 가지는지,
왜 Coroutine이 표준이 되었는지도 자연스럽게 이해할 수 있다.

profile
안드로이드... 좋아하세요?

0개의 댓글