본문 바로가기
Mobile App

[안드로이드] - Android Main Thread & Handler

by Jman 2022. 7. 15.
안드로이드 애플리케이션이 실행되면 안드로이드 시스템은 하나의 실행 스레드로 애플리케이션의 프로세스를 실행한다.
하지만, 애플리케이션의 구성요소가 생성될 때도 별도의 스레드가 생성되는 것은 아니다.
구성요소가 생성될 때에도 앞서 애플리케이션 실행될 때 실행된 스레드에서 실행된다.
이 스레드를 Main Thread 라고 한다.
안드로이드 시스템에 의해 생성된 Main Thread 는 화면 구성에 관한 역할을 담당한다.

예를 들어 UI도구 키트 구성 요소(TextView, EditText, Button, 등)를 생성과 조작할 때 움직이는 스레드가 Main Thread 이다.
그래서, Main Thread 를 UI Thread 라고도 한다.

 

Main Thread

  • 화면의 UI를 그리는 처리 담당을 한다.
  • UI와 상호작용을 하고 이벤트 결과를 사용자에게 보여준다.
  • UI 이벤트 작업에 일정시간 동안 응답이 없으면 ANR(Appication Not Responding) 상태로 전환시킬 수 있다.

 

최소 하나의 스레드가 실행 중이어야만 다른 새로운 스레드를 만들 수 있다.

우리가 알고 있는 main() 함수에서 프로세스의 시작과 동시에 실행되는 스레드가 메인 스레드이다.

 

프로세스가 시작될 때, 최초의 실행 시작점이 되는 main() 와 순차적으로 진행되는 flow 또한 하나의 스레드다.

메인 스레드에서 오해할 수 있는 점이 있다. "오직, 메인 스레드에서 새로운 스레드를 만들어 실행할 수 있다" 라는 말은 거짓이다.

기존에 실행 중인 스레드에서 새로운 스레드가 실행될 수 있다가 맞는 말이다.

단지 메인 스레드는 프로세스가 가지는 최초의 스레드일 뿐이다.

 

 

보통 프로그램은 하나의 일만 할 수 없다.

그렇게 되면 대기시간이 길어지면서 프로그램의 효율이 많이 떨어지기 때문이다.

그렇기 때문에 우리는 멀티 스레드 방식으로 작업이 동시수행 되도록 해야한다.

여기서 주의해야할 점이 있다.

UI를 만드는 스레드가 메인 스레드인데, 다른 스레드가 UI를 변경하려고 한다면 문제가 생긴다. (그림1 참고)

안드로이드 시스템은 메인 스레드 이외에 다른 스레드가 UI를 변경해버리면 교착상태, 경합상태 등 여러 문제가 발생된다.

그렇기 때문에 안드로이드 시스템에서는 외부 스레드가 UI를 변경하려고 한다면 반드시 이벤트 핸들러를 거처야 한다.

 

 

그림1

 

시간이 오래걸리는 작업은 UI 스레드로 부터 분리하고,

별도의 스레드에서 실행함으로써 UI 스레드 작업이 지연(또는 차단)되는 것을 방지해야 한다.

그렇게 하기 위해선 다른 스레드에서 메인 스레드로 결과를 전송하는 방식을 사용해야 한다.

즉, 스레드 간의 통신을 구현해야한다.

안드로이드 시스템에서 스레드간 통신을 위해, Looper와 Handler라는 장치를 제공해준다.

 

Looper & Handler

Looper

Looper 는 자신을 생성한 스레드의 Message Queue 에 들어오는 Message, Runnable 를 순서대로 Handler 에 전달하는 역할을 한다.

이때, 메인 메서드는 Looper 가 기본적으로 생성되어 있지만, 새로 생성한 다른 스레드는 Looper 를 생성하지 않기에, Message 또는 Runnable 받으면, Looper 를 생성해야 한다.

(prepare() 메서드를 통해서 Looper 를 생성하고, loop() 메서드를 통해 Looper 가 무한 로프를 돌도록 만들어 Message Queue 를 관리하도록 해준다.)

 

하나의 스레드에는 오직 하나의 Looper 를 가지며, Looper 는 오직 하나의 스레드만 담당한다.

안드로이드 시스템에서 기본적으로  MainActivity 가 실행됨과 동시에 자동으로 메인 스레드의 Looper 가 돈다.

그리고 메인 스레드의 Looper 는 보통 UI 작업을 위한 메세지를 처리한다.

Handler

Handler 는 스레드의 Message Queue 에서 던져주는 Message 또는 Runnable 을 처리하는 역할을 한다.

이 때, Handler 객체는 Handler 를 생성한 해당 스레드의 객체에 종속(바인드) 된다.

 

Looper와 Handler 는 어떤식으로 작동할까?

  • 메인 스레드는 내부적으로 Looper 를 가지며, 그 안에는 Message Queue 가 포함된다.
  • Message Queue는 스레드가 다른 스레드나 혹은 자기 자신으로부터 전달 받은 Message를 기본적으로 선입선출 형식으로 보관화는 Queue 이다.
  • LooperMessage Queue 에서 Message 나 Runnable 를 차례로 꺼내 Handler 가 처리하도록 전달한다.
  • Handler 는 Looper 로부터 받은 Message 를 실행, 처리 하거나 다른 스레드로부터 메시지를 받아서 Message Queue에 넣는 역할을 한다.

 

* 메세지 큐(Message Queue) : 안드로이드에서 하나의 작은 처리 단위 (메세지 큐에 메세지를 넣으려면 Handler 객체를 사용)

* Message 객체 : 스레드 간 통신할 내용을 담는 객체. 

* Runnable 객체 : Looper 가 실행시킬 Runnable 인터페이스를 구현한 객체

-> Message 는 스레드 간 통신하고자 하는 내용을 담는 것이며, Runnable 은 해당 Handler Thread 가 실행할 run() 메서드와 그 내부에서 실행될 코드를 담는다는 차이가 있다.

 

https://medium.com/@ankit.sinhal/handler-in-android-d138c1f4980e

 

 

AsyncTask (비동기 태스크)

AsyncTask 는 Thread 나 Message Loop 등의 작동 원리를 몰라도, 하나의 클래스에서 UI 작업과 Background 작업을 쉽게 할 수 있도록 안드로이드에서 제공하는 클래스다.

캡슐화가 잘 되어 있기 때문에 사용 시 코드 가독성이 증대되는 장점이 있으며, Task schedule 을 관리할 수 있는 Callback Method 를 제공하고, 필요할 때 쉽게 UI 갱신도 가능하며 작업 취소도 쉽다.

따라서 리스트에 보여주기 위한 데이터 다운로드 등 UI 와 관련된 독립된 작업을 실행할 경우 AsyncTask 로 간단하게 구현할 수 있다.

하지만, AsyncTask 를 사용해서 스케줄링 할 수 있는 작업 수의 제한이 있다.

몇 초 정도의 짧은 작업에서만 이상적으로 동작한다는 한계가 존재한다.

또한, 안드로이드 버전 별로 병렬 처리 동작이 다르므로, 허니콤 이후 버전에서 멀티 스레드로 병렬적인 동작을 원한다면 AsnycTask 를 실행할 때 AsnycTask.THREAD_POOL_EXECUTOR 스케줄러를 지정해야 한다.

 

 

참고 

https://academy.realm.io/kr/posts/android-thread-looper-handler/

https://www.crocus.co.kr/1566

https://itstudy-mary.tistory.com/220

https://junghun0.github.io/2019/09/07/android-thread-handler/