안드로이드에서의 UI는 기본적으로 싱글스레드 모델로 작동하기 때문에 이러한 영향을 고려해서 개발하지 않으면 성능 저하가 일어날 수 있기 때문에 메인스레드에서의 작업을 최대한 피해야한다.
만약, 긴 시간이 걸리는 작업을 메인스레드에서 작업한다면 애플리케이션의 성능이 저하됨은 물론이고, 지나치게 많은 시간이 걸리는 경우 ANR(Application Not Responding)상태로 전환이 될 수 있다. 따라서 시간이 걸리는 작업을 수행해야 할 때에는 여분의 스레드를 활용하여 메인스레드와 분리해서 작업해야하고 자연스럽게 메인스레드와 다른 스레드가 통신하는 방법이 필요하다.
Looper와 Handler의 사용 목적
메인 스레드와 다른 스레드가 어떤 TextView의 setText를 시도한다고 하면 둘 중 어느 스레드에서 setText가 적용될 지 예측할 수 없고 둘 중 하나의 값은 버려지게 된다. 이와 같이 두개 이상의 스레드에서 사용할 때의 동기화 이슈를 차단하기 위해서 Looper와 Handler를 사용하게 된다.
Looper와 Handler의 작동 원리
메인스레드는 내부적으로 Looper를 가지고 있으며 그 안에는 Message Queue가 포함된다. Message Queue는 스레드가 다른 스레드나 혹은 자기 자신으로 부터 전달받은 Message를 선입선출하는 방식으로 전달하는 Queue이다. Looper는 Message Queue에서 Message나 Runnable 객체를 차례로 꺼내 Handler가 처리하도록 전달한다. Handler는 Looper로부터 받은 Message를 실행, 처리하거나 다른 스레드로부터 메시지를 받아서 Message Queue에 넣는 역할을 하는 스레드간의 통신 장치이다.
Handler와 Looper의 동작과정
- 메시지를 handler의 sendMessage()를 통해 handler에게 전달된다.
- handler에게 전달된 메시지는 Looper의 MessageQueue에 들어간다.
- Looper의 loop() 메소드를 통해서 처리할 메시지를 Handler에게 보낸다.
- Handler는 handleMessage() 메소드를 통해 메시지를 처리한다.
Handler
Handler는 스레드의 Message Queue와 연계하여 Message나 Runnable 객체를 받거나 처리하여 스레드간의 통신을 할 수 있도록 한다. Handler 객체는 하나의 스레드의 Message Queue에 종속된다. 새로 Handler 객체를 만든 경우 이를 만든 스레드와 해당 스레드의 Message Queue에 바인드된다. 다른 스레드가 특정 스레드에게 메시지를 전달할려면 특정 스레드에 속한 Handler의 post나 sendMessage 등의 메서드를 호출하면 된다. Message Queue는 선입선출의 방식으로 보관하긴 하지만, 전달시점에 다른 메서드를 이용하여 Queue의 맨앞으로 보내거나 지연시킬 수도 있다.
Looper
Looper는 무한히 루프를 돌며 자신이 속한 스레드의 Message Queue에 들어온 Message나 Runnable 객체를 차례로 꺼내서 이를 처리할 Handler에게 전달하는 역할을 한다. 메인 스레드는 기본적으로 Looper가 생성되어 있지만, 새로 생성한 Thread는 기본적으로 Looper를 가지고 있지 않고 단지 run 메서드만 실행한 후 종료하기 때문에 메시지를 받을 수 없다. 따라서 기본 스레드에서 메시지를 전달받으려면 prepare() 메소드를 통해 Looper를 생성하고 loop()메소드를 통해 Looper가 무한히 루프를 돌며 Message Queue에 쌓인 Message나 Runnable 객체를 꺼내 Handler에 전달하도록 한다. 이렇게 활성화된 Looper는 quit()이나 quitSafely()메소드로 중단할 수 있다. quit()메소드가 호출되면 Looper는 즉시 종료되고, quitSafetly() 메소드가 호출되면 Message Queue에 쌓인 메시지들을 처리하고 종료된다.
Message와 Runnable
Message란 스레드가 통신할 내용을 담은 객체이자 Queue에 들어갈 일감의 단위로 Handler를 통해 보낼 수 있다. 일반적으로 Message가 필요할 때 새 Message 객체를 생성하면 성능 이슈가 생길 수 있으므로 안드로이드가 시스템에 만들어 둔 Message Pool 객체를 재사용한다.
Runnable을 설명하려면 스레드를 만드는 두가지 방법을 이해해야 한다. 새 스레드는 Thread() 생성자를 만들어서 내부적으로 run()을 구현하던지 Thread 생성자로 만들어서 Runnable 인터페이스를 구현한 객체를 생성하여 전달하던지 두 가지 방법 중 하나를 선택하여 생성한다. 후자에서 사용하는 방법이 Runnable로 스레드의 run() 메소드를 분리한 것이다. 따라서 Runnable 인터페이스는 run() 추상 메소드를 가지고 있으므로 상속받은 클래스는 run()코드를 반드시 구현해야 한다. Message는 통신할 내용을 담는다면 Runnable은 실행할 run()메소드와 그 내부에서 실행될 코드를 담는다는 차이점이 있다.
HandlerThread
안드로이드의 스레드는 Java의 스레드를 사용하기 때문에 내부적으로 Looper를 가지고 있지 않다는 점이 불편한 점이었다. 이같은 불편한 점을 개선하기 위해 Looper를 보유하는 스레드 클래스를 만들었는데 이것이 바로 HandlerThread이다
참조: https://academy.realm.io/kr/posts/android-thread-looper-handler/?w=1