안드로이드 스레드(Android Thread)

2018. 10. 5. 17:47


1. 스레드(Thread), 그리고 프로그램(Program)과 프로세스(Process).


스레드(Thread)란 무엇인가


스레드(Thread)를 설명하는 이 문장을 읽고 나서, 어떤 용어 또는 개념들이 머리 속에서 확장되나요?


스레드(Thread)로부터 확장된 개념


스레드(Thread)라는 단어를 듣는 것 만으로, 그림에 나열한 용어들이 자연스럽게 머리 속에 떠오르나요? 만약 이러한 용어들에 대한 개념을 정확히 이해하고, 또 프로그램을 개발하는 과정에서 이들과 관련된 이슈를 다루어 본 경험이 있다면, 굳이 아래에 서술한 내용을 살펴볼 필요가 없을 것 같습니다. 본문 중에서 관심있는 내용은 예제 소스 코드 정도겠지요.


하지만 스레드에 대한 내용을 처음 접했거나, 멀티 스레드(Multi Thread)와 관련된 프로그래밍을 경험해보지 않았다면, 이 글에서 설명하는 내용들이 자신의 프로그래밍 능력을 향상시키는데 있어 조금은 도움이 될 수 있을 것 같습니다.


자, 그럼, "정보통신학개론"과 같은 컴퓨터 정보통신 기초 과정에서 배우는 프로그램(Program)과 프로세스(Process)에 대한 언급을 시작으로, 스레드에 대한 내용을 정리해보도록 하겠습니다.


1.1 프로그램(Program)과 프로세스(Process)

프로그램(Program)은 컴퓨터에 의해 실행되는 명령과 데이터의 집합을 말합니다. 시스템의 저장 장치(Storage Device)에 파일 형태로 저장되어 있으며, 중앙처리장치(CPU)에 의해 메모리(MEMORY)로 로딩되고 실행됩니다. 그런데 사실, 프로그램이 반드시 파일로 저장되는 것은 아닙니다. 과거에 파일(File)이라는 개념이 등장하기 전부터 천공 카드(Punched Card) 또는 테이프(Tape) 같은 미디어에 프로그래밍된 형태로 존재해왔고, 현재도 부트로더(Bootloader)처럼 ROM에 바이너스(Binary) 형식으로 바로 실행되는 프로그램도 있습니다.


컴퓨터 기술의 발전이 상대적으로 더디게 이루어지던 컴퓨팅 역사의 초기에는, 컴퓨터가 동시에 하나의 프로그램만 실행할 수 있었습니다. A 프로그램 실행 중 B 프로그램을 실행하려면, A를 먼저 종료해야만 B를 실행할 수 있었죠. 하지만 컴퓨터 아키텍쳐의 진화와 컴퓨팅 기술 발전을 통해 상대적으로 빠른 속도의 CPU와 MEMORY의 효율적 사용 방법들이 연구되었고, 결국 동시에 여러 프로그램이 실행 가능한 멀티 프로그램(Multi Program, 실행 중인 프로그램을 종료하지 않아도 다른 프로그램을 실행할 수 있는) 환경이 만들어지게 되었습니다.


그런데 운영체제(Operating System)가 빠르게 발전되면서, 소프트웨어 분야에 조금 더 획기적인 변화가 일어나게 됩니다. 기존에 프로그램이 컴퓨터 하드웨어의 의해 직접적으로 실행되던 구조에서, 운영체제가 프로그램의 실행을 담당하는 구조로 변화된 것이죠. 즉, 컴퓨터 하드웨어 리소스(CPU, MEMORY, ...)에 대한 관리를 담당하는 운영체제가 프로그램의 실행을 관리하게 됨으로써, 물리적인 공간(디스크)에 저장되어 있는 프로그램을 논리적인 공간(메모리)에서 실행하고, 리소스 할당과 관리를 운영체제가 담당하게 된 것입니다.


시스템 리소스가 특정 프로그램에 의해 관리되지 않는 것, 그리고 프로그램이 운영체제에 의해 논리적인 공간에서 실행된다는 것은 시스템 효율 측면에서 매우 큰 장점을 가져다 줍니다. 바로 "여러 개의 프로그램을 동시에" 또는 "하나의 프로그램을 동시에 여러 개" 실행할 수 있게 해준다는 것이죠. 그리고 이렇게, 현재 메모리에 로딩되고 실행 중인 프로그램을 프로세스(Process) 라고 부릅니다.


정리하자면, 프로그램(Program)은 명령과 데이터로 구성되어 저장 장치(Storage Device)에 저장된 형태의 실행 코드를 말하고, 프로세스(Process)는 운영체제에 의해 메모리(MEMORY)에 적재되어 실행 중인 프로그램을 의미합니다.

프로그램(Program)과 프로세스(Process)


1.2 프로세스(Process)와 스레드(Thread)

운영체제에 의해 프로그램이 메모리에 로드되고 프로세스가 실행되면, 프로세스는 자신의 코드 시작점부터 시작하여 종료지점까지 순차적인(Sequential) 실행 흐름을 가집니다. 흔히 main() 이라고 부르는 함수에서 시작하여 조건문과 반복문 등을 거쳐 main()의 종료 지점까지 코드 순서대로 실행되는 것이죠.


그런데 어떤 경우에는, main() 함수에서 시작되는 하나의 코드 실행 흐름만으로 처리하기 힘든 동작을 구현해야 하는 상황이 발생할 수 있습니다.


음, 예를 하나 들어볼까요? 프로세스의 main() 함수에서, USB 메모리에 저장된 파일들을 하드디스크로 옮기는 상황을 가정해보죠. 뭐, 큰 고민없이 main() 함수에 파일 복사 기능을 구현하면 됩니다. 하나의 코드 실행 흐름 속에서 순차적으로 실행되도록 말이죠.


그런데 여기에 추가적으로, USB 메모리의 파일을 복사함과 동시에, 네트워크를 통해 수신되는 파일도 복사해야 한다면 어떻게 해야 할까요? 분명 main() 함수의 실행 흐름은 USB 메모리의 파일을 복사하는 중이기 때문에, 실시간으로 네트워크를 통해 수신되는 파일을 복사하는 코드가 실행될 수 없게 됩니다.


이런 경우, 직관적으로 떠올릴 수 있는 방법은, USB 메모리의 파일을 복사하는 실행 흐름에 더하여 네트워크로부터 수신되는 파일을 저장하는 또 하나의 실행 흐름을 만드는 것입니다. 즉, main() 함수의 실행 흐름과 구분되는, 그리고 동시에 실행가능한 추가적인 실행 흐름을 만들고 실행하면, 두 가지 작업을 동시에 처리할 수 있게 되는 것이죠.


바로 이렇게 프로세스 내에서 실행되는 각각의 "독립적인 실행 흐름", 이것을 스레드(Thread)라고 부르며, 하나의 프로세스 내에서 두 개 이상의 스레드가 동작하도록 프로그래밍하는 것을 멀티스레드 프로그래밍(Multi-Thread Programming)이라고 합니다.


1.3 메인 스레드(Main Thread)

스레드가 각각의 독립적인 실행 흐름을 나타낸다고 하더라도, 프로세스가 실행될 때 모든 스레드가 한번에 실행된다거나, 스레드를 실행할 수 있는 특별한 추가 요소가 존재하는 것은 아닙니다. 스레드는  프로세스 실행 중 언제든지 필요에 따라 만들어지고 실행될 수 있는데, 이는 기존에 이미 실행 중인 스레드에 의해서 수행될 수 있습니다.


여기서, 다른 것들과 구분되는 특징을 가진 하나의 스레드에 대해 언급하고 넘어가야 할 필요가 있을 듯 한데요, 앞서 새로운 스레드가  이미 실행되고 있는 스레드를 통해 실행된다고 설명하였습니다. 그렇다면 프로세스가 시작될 때, 최소 하나의 스레드가  실행 중이어야만 다른 새로운 스레드를 만들 수 있다는 얘기가 되겠죠. 네, main() 함수라고 일컫는, 프로세스의 시작과 동시에 실행되는 스레드, 메인 스레드(Main Thread)입니다.


프로세스가 시작될 때, 최초의 실행 시작점이 되는 main() 함수, 그리고 그 곳부터 순차적으로 진행되는 실행 흐름, 이 또한 하나의 스레드이며, 메인 스레드(Main Thread)라고 부릅니다. 프로세스의 시작과 동시에 무조건 실행되는, 프로세스의 가장 중요한 스레드이기 때문에 메인 스레드라고 부르는 것이죠.


그런데 메인 스레드의 존재에 대해 따로 언급했다고 해서, "새로운 스레드가 오직 메인 스레드에 의해서만 실행될 수 있다"고 오해하면 안됩니다. 다시 한번 강조하지만, 스레드는 "기존에 실행 중인 스레드"에서 실행될 수 있습니다. 단지, 메인 스레드는 프로세스가 가지는 최초의 스레드일 뿐이죠.


2. 안드로이드 스레드(Thread)

안드로이드 앱을 만들 때 사용되는 많은 개발 요소가 그러하듯, 안드로이드의 스레드 또한 자바 SDK에 포함된 API를 사용합니다. 스레드라는 이름 그대로, Thread(java.lang.Thread) 클래스를 사용하여 스레드 관련 기능을 구현할 수 있습니다.


Thread 클래스를 사용하여 새로운 스레드를 생성하고 실행하는 방법은 크게 두 가지가 있습니다. 하나는 Thread 클래스를 상속(extends)한 서브클래스(subclass)를 만든 다음, Thread클래스의 run() 메서드를 오버라이드(override)하는 것이고, 다른 하나는 Runnable 인터페이스를 구현(implements)한 클래스를 선언한 다음, run() 메서드를 작성하는 것입니다.


그리고 공통적으로, 각 방법을 통해 생성된 클래스 객체의 run() 메서드를, Thread 클래스의 start() 메서드를 통해 실행해줌으로써 스레드의 생성 및 실행 과정이 완료됩니다.


글로만 설명하니, 이해하기가 쉽지 않죠? 그리고 Thread 클래스에 더하여, Runnable 인터페이스까지 나오니 살짝 헷갈리기도 하네요. 음, Thread 클래스의 상속(extends), Runnable 인터페이스의 구현(implements), 둘 중 어떤 걸 사용하는 게 좋을지 고민되기도 하고...


자, 그럼 스레드를 만드는 두 가지 방법, Thread 클래스를 상속(extends)하는 방법과 Runnable 인터페이스를 구현(implements)하는 방법에 대해 좀 더 자세히 살펴보고, 두 가지 방법의 차이점에 대해 알아보도록 하겠습니다.

2.1 Thread 클래스 상속(extends)

스레드를 만드는 첫 번째 방법은, Thread 클래스를 상속(extends)한 클래스를 만들고 run() 메서드를 오버라이드(override)한 다음, 클래스 인스턴스를 생성하고 start() 메서드를 호출하는 것입니다.

Thread 클래스 상속(extends Thread)으로 스레드 생성하기


    class NewThread extends Thread {
        NewThread() {

        }

        public void run() {
            // TODO : thread running codes.
        }
    }

    NewThread nt = new NewThread() ;
    nt.start() ;

정말 간단하죠? Thread를 상속(extends), run() 메서드 구현, start() 메서드 호출. 이 간단한 몇 줄의 코드만으로 새로운 스레드를 만들고 실행할 수 있습니다.


다음, Runnable 인터페이스를 상속(implements)하는 방법에 대해 알아볼까요?

2.2 Runnable 인터페이스 구현(implements)

Runnable 인터페이스를 구현(implements)하는 방법도 Thread 클래스를 상속(extends)하는 방법만큼 간단합니다. Runnable 인터페이스를 구현(implements)하는 클래스를 선언하고 run() 메서드를 구현한 다음, 클래스 인스턴스를 Thread 클래스 인스턴스의 생성자에 전달하고 Thread 클래스 인스턴스의 start() 메서드를 호출하면 됩니다.

Runnable 인터페이스 구현(implements Runnable)하여 스레드 생성하기

    class NewRunnable implements Runnable {
        NewRunnable() {

        }

        public void run() {
            // TODO : thread running codes.
        }
    }

    NewRunnable nr = new NewRunnable() ;
    Thread t = new Thread(nr) ;
    t.start() ;

음, 그런데 Runnable 인터페이스를 구현(implements)하는 방법이, 앞서 Thread 클래스를 상속(extends)하는 방법보다 할 일이 조금 더 많아 보이네요. Runnable 인터페이스를 구현한 것에 더해 Thread 클래스의 인스턴스를 생성한 다음 start() 메서드를 호출해야 하는군요.


상황이 이렇다면 스레드를 실행할 때는 무조건 Thread 클래스를 상속(extends)하는 게 낫지 않을까요? 조금이라도 간단한 방법을 선택하는 게 좋기도 하고, "스레드를 만들기 위해 Thread 클래스를 상속한다"는 것이 좀 더 직관이니까요.


그렇다면 Runnable 인터페이스는 왜 만들어둔 것일까요? 아니, 애초에 Thread 클래스를 상속(extends)하는 방법과 Runnable 인터페이스를 구현(implements)하는 방법은 무슨 차이가 있을까요?


두 가지 방법이 어떤 차이가 있는지, 스레드를 실행할 때 어떤 방법을 선택해야 하는지를 결정하기 위해서는, 기본적인 자바 언어의 객체지향 개념, 그 중에 클래스 상속(extends)과 인터페이스 구현(implements)의 차이를 조금 더 이해할 필요가 있습니다.

2.3 Thread vs Runnable

먼저, 구현 방법의 차이를 살펴보기에 앞서, Thread 클래스를 상속(extends)해서 만든 스레드와 Runnable 인터페이스를 구현(implements)해서 만든 스레드는, 작성된 run() 메서드 코드의 실행과 성능이 동일하다는 것을 알아두시기 바랍니다. 단지 구현 과정이 차이가 날 뿐이라는 것이죠.


객체지향 프로그래밍(OOP, Object-Oriented Programing)에서 클래스(Class)를 상속한다는 것은, 부모 클래스(Class)의 특징을 물려받아 재사용(re-use)하는 것을 기본으로, 부모의 기능을 재정의(override)하거나, 새로운 기능을 추가하여 클래스를 확장(extend)하는 것을 의미합니다. 그리고 이 내용을 달리보자면, 클래스(Class)의 기능을 재정의(override)하거나 확장(extend)할 필요가 없다면, 굳이 클래스(Class)를 상속하지 않아도 된다는 말이 되죠. 그냥 기존 클래스를 그대로 사용하면 되는 것입니다.


자, 그럼 이제, 상속에 대한 개념을 떠올리며, Thread 클래스를 상속(extends)하여 스레드를 실행하는 방법에 대한 내용으로 돌아가 보겠습니다.


스레드를 실행하기 위해서는 Thread 클래스가 제공하는 기능을 사용해야 합니다. 그리고 스레드로 실행될 메서드도 재정의(override)해야 하죠. 그래서 Thread 클래스를 상속(extends)하고 스레드 실행 메서드인 run() 메서드를 오버라이드(override)한 클래스를 만든 것입니다.

Thread 클래스를 상속(extends Thread)하여 스레드를 생성하는 코드


그런데 여기서, 한 가지 고민이 생기네요. 단지, run() 메서드 하나를 위해 Thread 클래스를 상속해야 할까요? Thread 클래스의 다른 모든 기능들은 그대로 재사용(re-use)하는데, 무조건 오버라이드(override)되어야 하는 run() 메서드만을 위해서? 굳이 Thread 클래스를 상속하지 않고, run() 메서드 코드만을 작성해서 Thread의 start() 메서드로 전달할 수 있으면 좋을텐데 말이죠. 게다가, 프로그램 설계에 따라, run() 메서드를 구현할 클래스가 반드시 Thread가 아닌 다른 클래스를 상속해야 한다면, 다중상속이 허용되지 않는 자바에서 Thread 클래스를 상속(extends)하여 스레드를 만드는 것이 불가능해질 것입니다.


자, 이런 문제들을 해결하기 위해 개발자가 선택할 수 있는 방법은 무엇일까요? 스레드 생성 시 반드시 구현해야 할 run() 메서드를 Thread 클래스와 분리하고 그 구현을 강제하는 것, 그리고 추상화되어 있는 메서드를 클래스에서 구현(implements)하도록 만듦으로써, 다중상속이 불가능한 자바에서 그와 유사한 효과를 낼 수 있도록 만드는 방법. 바로, 인터페이스(Interface)를 사용하는 것이죠. 그리고 그 인터페이스가 Runnable 인터페이스입니다.


Runnable 인터페이스는 abstract로 선언된 단 하나의 메서드, run() 메서드만을 가집니다. 그리고 개발자는 Runnable 인터페이스를 implements 하는 클래스를 만들고 run() 메서드를 구현한 다음, Thread 의 생성자에 전달하고 start() 메서드를 호출함으로써 스레드를 실행할 수 있습니다.

Runnable 인터페이스를 구현(implements Runnable)하여 스레드를 생성하는 코드


그럼 스레드를 만들 때는 무조건 Runnable 인터페이스를 사용하면 되는 것일까요? 또 반드시 그런 건 아닙니다. 만약 run() 메서드 외에 Thread 클래스의 기능을 재정의해야 하거나, 확장이 필요하다면 Runnable 인터페이스 대신 Thread 클래스를 상속(extends)해야 합니다.


정리하자면, 두 가지 방법 중 어떤 것을 선택할 것인지는 Thread 클래스 기능의 확장 여부에 따라 결정할 수 있습니다. 단순히 run() 메서드만을 구현하는 경우라면 Runnable 인터페이스를 구현(implements)하고, 그 외 Thread 클래스가 제공하는 기능을 오버라이드(override)하거나 확장해야 한다면 Thread 클래스를 상속(extends)하는 방법을 선택할 수 있습니다.


항목Runnable 인터페이스 구현Thread 클래스 상속
코드implements Runnableextends Thread
범위단순히 run() 메서드만 구현하는 경우.Thread 클래스의 기능 확장이 필요한 경우.
설계논리적으로 분리된 태스크(Task) 설계에 장점.

태스크(Task)의 세부적인 기능 수정 및 추가에 장점.

상속Runnable 인터페이스에 대한 구현이 간결.Thread 클래스 상속에 따른 오버헤드.

물론, 이 선택 기준이 절대적인 것은 아닙니다. 개발자의 취향과 코드 작성 정책, 또는 여러 가지 상황에 맞춰 적절한 방법을 선택하면 됩니다.

3. 안드로이드 앱의 메인 스레드(Main Thread)

앞서 "1.3 메인 스레드 (Main Thread)"에서, 프로세스가 시작될 때 반드시 실행되는 스레드인 메인 스레드(Main Thread)에 대해 언급했습니다. 프로세스의 시작점인 main() 함수에서 시작되는 스레드를 프로세스의 주 실행 흐름, 이것을 메인 스레드라고 하는데요, 그런데 안드로이드 스튜디오를 통해 생성한 안드로이드 앱 프로젝트 소스를 보면 main() 함수를 찾을 수 없습니다. 스레드의 시작점이 되는 함수 자체를 찾을 수가 없죠.


그렇다면 안드로이드 앱에는 main() 함수가 존재하지 않는 것일까요? 만약 main() 함수가 존재하지 않는다면 앱의 시작점은 어디일까요? 그리고 안드로이드 앱에는 메인 스레드가 없는 것일까요? 그러면 새로운 스레드는 언제,어디서 만들고 실행할 수 있을까요?


이 물음들에 대한 답을 얻기 위해서는, 안드로이드 가상 머신(Virtual Machine)이 찾는 앱의 시작점이 무엇인지, 그리고 앱의 메인 스레드가 어떻게 동작하는지 알아볼 필요가 있습니다.

3.1 안드로이드 앱의 시작점(entry point).

일반적인 자바 프로그램을 작성할 때는, 프로그램의 시작점인 main() 함수를 반드시 구현해야 합니다. 이는 자바 가상 머신(JVM, Java Virtual Machine)이 자바 프로그램을 실행할 때, 프로그램의 시작점인 main() 함수를 찾아서 실행하기 때문입니다.


그런데 안드로이드 앱을 만들 때는, 상황이 조금 다릅니다. 같은 자바 언어를 사용하고, 동일한 자바 개발 도구(JDK, Java Development Kit)로 빌드하지만, 안드로이드 프로젝트에서는 개발자가 직접 main() 함수를 구현할 필요가 없습니다. 대신, 앱에 포함된 액티비티 중 하나를 런처(Launcher)로 지정함으로써, 해당 액티비티를 앱의 시작점(entry point)으로 만들 수 있습니다.


액티비티를 런처(Launcher)로 지정하는 코드는, 아래 그림과 같이 프로젝트의 "AndroidManifest.xml" 파일에서 확인할 수 있습니다.

AndroidManifest.xml에서 액티비티를 런처로 지정


하지만 개발자가 main() 함수를 직접 구현하지 않아도 된다고 해서, 안드로이드에 main() 함수가 존재하지 않거나, 앱이 실행될 때 아무런 준비 과정 없이, 무작정 런처로 지정된 액티비티가 실행되는 것은 아닙니다. 단지 개발자가 직접 main() 함수를 구현하지 않아도 된다는 것일 뿐, main() 함수와 그 실행 코드는 안드로이드 프레임워크(Framework)에 이미 구현되어 있습니다.


안드로이드 프레임워크에서, main() 함수는 "android.app.ActivityThread" 클래스에 구현되어 있습니다. ActivityThread의 main() 함수에는 안드로이드 프레임워크(Framework) 상에서 앱의 동작에 필요한 여러 준비 동작을 수행하는데, 그 중 가장 중요한 과정이 바로 메인 UI 스레드(Main UI Thread)를 실행하는 것입니다. 그리고 런처(Launcher)로 지정된 액티비티를 찾아서 실행해주죠.

3.2 안드로이드 메인 UI 스레드

보통 사용자의 입력(키보드, 마우스, 터치 등)이 중요하게 취급되는 기기에서 하나의 프로그램이 실행되면, main() 함수의 시작부터 종료까지 선형적으로 실행되고 끝나는 경우는 거의 없습니다. 물론, 간단한 유틸리티 프로그램이나, 사용자 입력이 필요하지 않은 프로그램은 시작에서 종료까지 단방향 실행의 선형적 구조를 가지지만, 사용자 입력이 필수인 프로그램에서는 사용자 입력 이벤트를 처리하기 위한 루프(Loop)가 실행되어야 합니다.


그런데 루프(Loop)를 실행한다고 해서, 무작정 for 문이나 while 문을 실행하는 것은 아닙니다. 루프(Loop) 안에서 사용자 입력이 들어올 때까지 대기해야 한다면 다른 이벤트를 처리할 수 없고, 단순히 사용자 입력이 존재하는지 여부를 체크만하고 지속적인 루프 실행을 반복한다면 그 자체가 시스템 자원을 소모시킬 수 있기 때문에, 루프(Loop)의 코드는 신중하게 작성되어야 합니다.


이런 경우를 위해, UI 프레임워크는 메시지 큐(Message Queue)를 사용하여 루프(Loop)의 코드를 작성하도록 가이드합니다. 메시지 큐(Message Queue)는 "큐(Queue)"라는 단어에서 알 수 있듯이, FIFO(First In First Out) 형식으로 동작하는 자료구조를 말합니다. 그리고 메시지(Message)는 사용자 입력을 포함한 시스템의 모든 이벤트를 전달할 때 사용하는 객체입니다. 그러므로 스레드의 관점에서 메시지 큐(Message Queue)는 시스템 이벤트를 발생 순서대로 전달받아 처리하기 위해 사용하는 구조를 의미합니다.


시스템(또는 프로세스)에서 발생한 새로운 메시지가 메시지 큐(Message Queue)에 수신되면, 메시지가 담고 있는 내용에 따라 적절한 핸들러(Handler) 메서드가 호출됩니다. 하지만 일반적인 응용 프로그램이, 시스템에서 발생 가능한 모든 이벤트를 처리할 필요는 없기 때문에, 보통, 자신이 관심을 가지는 이벤트에 대한 메시지를 핸들러로 등록하고 호출되도록 만듭니다.

일반적인 UI 프레임워크 동작 흐름


자, 여기까지가 일반적인 UI 프레임워크에서의 메인 UI 스레드 기본 동작에 대한 내용인데요. 안드로이드 앱의 메인 UI 스레드 동작 또한 이 범주를 벗어나지 않으며, 루프(Loop)와 핸들러(Handler)의 역할을 수행하는 클래스 구현을 통해 메인 UI 스레드를 작성할 수 있게 해줍니다.


먼저, 안드로이드 앱에서 시스템 이벤트를 처리하기 위한 루프(Loop)는 Looper라는 클래스를 통해 실행됩니다. 이름이 직관적이죠? 루프(Loop)를 실행하는 Looper. 이 Looper 클래스의 주 역할은 앞서 설명했듯이, 루프(Loop)를 실행하고 그 루프(Loop) 안에서 메시지 큐(Message Queue)로 전달되는 메시지가 존재하는지 검사하는 것입니다. 그리고 새로운 메시지가 도착하면, 해당 메시지를 처리할 핸들러(Handler) 메서드를 실행합니다.

Looper 클래스와 Handler 클래스


안드로이드의 핸들러(Handler)는, 이름 그대로, Handler 클래스가 담당합니다. 그런데 통상적인 개념에서 핸들러(Handler)는 메시지 수신 시 그 처리를 담당하는 역할만 수행하는데, 안드로이드에서 Handler는 메시지를 보내는 역할까지 포함합니다. 안드로이드의 HandlerLooper가 가진 메시지 큐(Message Queue)를 다룰 수 있기 때문에, 새로운 메시지를 보내거나, 수신된 메시지에 대한 처리를 담당하는 주체가 되는 것입니다.

안드로이드 스레드(Thread)에서 Looper 클래스와 Handler 클래스


좀 더 자세한 안드로이드 메인 UI 스레드 코드 구현을 확인하시려면, 안드로이드 SDK 프레임워크 소스에서 "core/java/android/app/ActivityThread.java" 파일의 내용을 살펴보시기 바랍니다.

3.3 안드로이드 메인 UI 스레드의 중요한 역할 : 화면 그리기.

안드로이드 뿐만 아니라, 대부분의 "그래픽 사용자 인터페이스(GUI, Graphical User Interface)"를 가진 프레임워크(Framework)가 그러하듯, 프레임워크가 수행해야 할 가장 중요한 기능 중 하나는 "다양한 구성 요소들을 화면에 그리는 것"입니다. 그리고 프레임워크의 "그리기 기능"은 반드시 메인 UI 스레드에서만 실행되어야만 합니다.


"응? 왜 화면 그리기 기능은 메인 UI 스레드에서만 실행되어야 하나요? 여러 스레드에 나눠 실행하면 더 효율적이지 않나요?" 라는 의문이 생길 수도 있을텐데요. 그 의문에 대한 답을 얻기 위해 아래와 같이, 하나의 색상으로 칠해진 배경 위에 이미지를 그린 다음, 또 그 위에 버튼이 올라간 형태의 화면을 표시하는 경우를 가정해 보겠습니다.


만약 이 화면을 그리기 위해 여러 스레드를 사용한다면, 배경에 지정된 색을 칠하는 스레드 A, 이미지를 그리는 스레드 B, 그리고 버튼을 그리는 스레드 C로 나눌 수 있을 것입니다.

메인 UI 스레드 역할 화면 그리기 스레드 실행 순서


자, 이제 각각의 스레드가 실행되어 화면을 그려야 할 순간이 왔는데요. 만약 운이 좋게도 아래 그림의 첫 번째 결과처럼, A-B-C 순서대로 스레드가 순차적으로 실행된다면 우리가 원하는 화면을 볼 수 있을 것입니다. 하지만 상대적으로 작은 크기를 그려야 하는 C 스레드가 먼저 실행되거나, 많은 데이터를 가진 이미지를 그려야 하는 B 스레드가 나중에 실행되어 버리면, 두 번째, 세 번째 그림처럼 의도하지 않은 화면을 표시하게 되어 버립니다.

메인 UI 스레드 역할 화면 그리기 스레드 실행 순서에 따른 결과


이렇듯, UI를 제대로 표시하기 위해서는 각 요소를 그리는 순서가 절대적으로 중요하기 때문에 반드시 하나의 스레드, 즉, 메인 UI 스레드에서 순차적으로 그리도록 만들어야 하는 것입니다.

4. 참고.

.END.


ANDROID 프로그래밍/THREAD