본문 바로가기
안드로이드/기타

안드로이드 쓰레드의 통신 과정(Looper, Handler, MessageQueue)

by 나이아카 2022. 9. 25.

 요즘은 안드로이드 프레임워크의 내부 구조에 관해 관심이 많아지기 시작했습니다. 아무래도 처음 접할때는 내부 구조보다는 당장 안드로이드 애플리케이션을 어떻게 구동하는지에 대해서 관심이 많아 일단 만들고 가꾸는데 초점을 뒀었고 주변에 안드로이드 개발자가 혼자 뿐이어서 스스로 잘 해나가고 있는 줄 알았는데, 근래 다른 사람들의 코드를 엿볼 기회가 생기면서부터 점점 제 코드가 안타깝게 느껴지고 있네요. 아직 많은 발전이 필요한 모자란 개발자로서 아키텍처 및 코드의 구성에 대한 공부도 해야겠고, 내부 구조에 대해서도 어느정도 알아야 한다는 것 때문에 시간이 참 모자란 것 같습니다. 조금 늦기는 했지만 초보 개발자로서 이제라도 한 발 나갈 기회를 얻었다는 점에 만족하며 계속 글을 쓰지 않을까 합니다.


 안드로이드에는 MainThread(UIThread라 불리는)가 존재합니다. 이는 동시성 문제때문에 안드로이드 프레임워크에서 MainThread(main 함수가 실행되는 스레드)에서만 UI를 수정할 수 있도록 했기 때문인데요. 안드로이드 개발 초창기에는 네트워크 통신이나 오랜 시간 동안 특정한 동작을 하는 코드의 경우에 MainThread에서 실행할 수 있긴 했으나 ANR의 주범이어서 개발자가 알아서 조심해서 분배했어야 했는데요. api가 11이 된 후부터는 MainThread에서 통신 관련 코드를 실행시키는 경우 NetworkOnMainThreadException이 발생하게 됩니다

 이와 같은 형태의 모델은 자연스럽게 개발자가 UIThread와 IOThread, 기타 다른 Thread들을 기능 별로 분리해서 기능이 동작한 이후 UIThread에 변화를 전달하는 형태로 코드를 작성하게 되었는데요. 이로 인해 Thread들 사이에서 자연스럽게 데이터를 통신해야 할 필요성이 생겨났습니다.

 이러한 Thread의 통신을 위해 안드로이드에서는 Looper와 Handler, 그리고 MessageQueue를 제공하고 있습니다. 먼저 각각이 무슨 역할을 하는 지 살펴보고 두 Thread가 어떤 순서로 통신을 하는지 알아보겠습니다.

 

Looper

 루퍼는 이름 그대로 계속해서 loop 하면서 메시지큐에 쌓인 메시지들을 처리하는 것을 의미합니다. 이때 메시지는 하나의 데이터(혹은 처리 과정)라고 볼 수 있으며, 이 메시지는 다른 Thread 및 Looper를 가진 본인 Thread가 Handler를 통해 던질 수 있고, 그것을 Handler를 통해 Looper 내부에 존재하는 MessageQueue에 쌓이게 됩니다.

 이때 Looper는 하나의 Thread에서 하나만 존재하며, Looper를 가진 Thread의 작업만을 반복적으로 수행하게 됩니다. UIThread의 경우 안드로이드의 실행시 자체적으로 Looper를 생성해주는 코드를 내장하고 있습니다. 하지만 다른 Thread를 생성할 경우에는 직접 Looper를 호출해줘야 합니다.

 

Handler

 핸들러는 Thread의 창구 같은 역할을 하는 녀석입니다. 핸들러에는 sendMessage, handleMessage, post 등의 메소드가 존재하고 있는데, 이를 통해 다른 Thread에게 메시지를 던지거나 던져진 메시지를 받거나 혹은 Runnable 객체를 송수신할 수 있습니다.  이때 핸들러는 다른 Thread의 핸들러에게 원하는 message 및 실행시킬 메소드를 전달하면 그 핸들러에서 미리 지정된 코드를 실행시키게 됩니다.(받은 메시지에 따라 다른 코드로 분기한다던가 하는 등) 핸들러는 핸들러를 생성시킨 Thread에 속해 메시지를 처리하게 되기 때문에 사용하고 싶은 Thread 내부에서 핸들러를 생성해주어야 합니다. 기본 생성자를 통해 생성된 핸들러는 종속된 Thread의 MessageQueue와 Looper를 사용하게 됩니다.

 다른 핸들러에게서 sendMessage로 메시지를 받으면 핸들러는 그 메시지를 바로 루퍼에게 전달하거나 Thread에서 실행시키는 것이 아니라 MessageQueue에게 전달하게 됩니다. 이를 Looper가 다시 꺼내서 Thread에 적용시켜주는 방식으로 진행됩니다.

 

MessageQueue

 메시지큐는 루퍼 안에 존재하면서 메시지를 FIFO(first in first out) 방식(Queue와 동일)으로 꺼낼 수 있게 저장하는 Queue입니다. Thread에서 무언가를 실행시키기 위해서는 기본적으로 MessageQueue에 들어갔다가 Looper를 통해서 밖으로 나와야 합니다. 

 

Thread 통신 과정

 화면에서 사용자가 버튼을 클릭하는 과정을 통해 서버에서 데이터를 호출해 화면에 뿌려주는 기능이 존재한다고 가정하겠습니다.

 먼저, MainThread에서 click event를 통해 MainThread의 Handler가 메시지를 SubThread에게 전달합니다.

 그렇게 메시지를 전달받은 SubThread의 Handler는 전달 받은 메시지를 SubThread의 메시지큐에 넣게 됩니다.

 메시지큐에 메시지가 들어가게 되면 SubThread의 Looper가 이를 감지해 메시지를 꺼내서 다시 SubThread의 Handler에게 전달합니다.

 전달 받은 메시지를 SubThread의 Handler가 처리하게 되고, 미리 작성된 코드를 통해 서버와의 통신을 시작합니다. 서버에게서 데이터를 받아온 Handler는 다시 MainThread의 Handler에게 메시지(데이터)를 전달합니다.

 MaiThread의 Handler는 위의 과정과 동일하게 전달 받은 메시지를 다시 메시지큐에 넣고, 이를 파악한 Looper가 메시지를 감지해 다시 Handler에 옮겨 화면에 데이터를 뿌려주는 작업을 하게 됩니다.

 위처럼, 핸들러는 각 Thread끼리의 데이터 전달 및 실행을 담당하고 루퍼는 메시지큐에 존재하는 메시지를 다시 핸들러에게 전달하는 역할을, 메시지큐는 Thread에서 사용되는 메시지를 저장하는 용도로 사용됩니다.


 글이 엄청 길어지지 않을까 하던 걱정은 어디로 갔는지, 정리하고 나니 매우 간단해졌습니다. 빠진 부분이 있지 않은지 고민될 정도로 짧아졌네요. 항상 정답이 간단하게 숫자로 표현되는 수학처럼 간단하다면 블로그에 글을 쓸 때도 점검하기가 참 쉽겠지만, 검토를 하더라도 글이 정답이라는 보장을 할 수 없으니 참으로 안타까운 것 같습니다. 언제든 혹시 제 글을 보게 되시는 고수분들이 틀린 부분을 확인한다면 댓글이나 메일, 뭐든 좋으니 연락해서 제 부족한 지식을 채워주시면 고맙겠습니다...

'안드로이드 > 기타' 카테고리의 다른 글

ViewBinding vs DataBinding  (0) 2023.01.02
viewLifecycleOwner.lifecycleScope vs lifecycleScope  (0) 2022.10.20
안드로이드 Application Class  (2) 2022.09.11
ANR(Application Not Responding)이란?  (0) 2022.08.06
LiveData vs RxJava  (0) 2022.07.08

댓글