iOS를 사랑하는 AOS 개발자

[Android] MainThread & Handler 본문

Android (이론)

[Android] MainThread & Handler

아사안개 2024. 5. 5. 22:54
반응형
SMALL

👼🏻 초보 안드로이드 개발자가 매번 구글링하기 싫어서 정리하는 블로그 👼🏻

안녕하세요! 🙋🏻‍♀️

Main Thread 와 Handler 에 대해 작성해보았습니다.

Main Thread + Handler

📌 일반적인 Main Thread

  • 프로세스 실행 중의 필요에 따라 Thread가 생성 및 실행된다.
  • Thread는 기존에 이미 실행되어있는 다른 Thread에 의해 생성 및 실행한다.
  • 최초의 ThreadMain Thread라고 부른다.Thread가 생성되고 시작되는 곳을 main() 함수라고 부른다.
  • 프로세스가 시작되어 프로세스의 시작점인 main()함수에서 실행되는 최초의 ThreadMain Thread가 된다.

📌 Android의 메인스레드

  • 앱에 포함된 액티비티 중 하나를 런처로 지정함으로써 앱의 시작점, 즉 앱 프로세스의 시작점을 지정해줄 수 있다.
  • AndroidManifest.xml 에 작성되는 런처를 지정하는 부분.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
       ...
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  • 안드로이드 앱의 main()은 안드로이드 프레임워크에 존재한다.
    • 안드로이드 프래임워크 내부 클래스인 android.app.ActivityThread가 애플리케이션의 메인 클래스다.
    • ActivityThreadmain()메서드가 애플리케이션의 시작 지점이다.
    • ActivityThread는 어떤 것도 상속하지 않은 클래스이다.
    • Activity만 관련되어있는 것이 아닌 모든 컴포넌트들이 다 관련되어 있다.
  • 안드로이드의 메인스레드의 주요 업무 중엔 UI 작업도 포함된다.
  • 안드로이드 메인스레드가 UI 작업을 할 땐, 단일 스레드 모델이 적용된다.
    // ActivityThread.java
    // AppCompatActivity → FragmentActivity → ComponentActivity → androidx.core.app.ComponentActivity → Activity → ActivityThread

    public static void main(String[] args) {
        /* ..*/
        Looper.prepareMainLooper();  // 1번
        /* .. */
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
		/* .. */   
		Looper.loop(); // 2번

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

📌 단일 스레드 모델

  • UI 작업에 있어 경합상태, 교착 상태를 방지하고자 메인스레드엔 단일 스레드 모델이 적용된다.
    • 하나의 위젯에 멀티 스레드를 사용한다고 하면 해당 상태의 문제가 발생 할 수 있다.
    • 하나의 Activity에 N개의 위젯이 있고 각 위젯에 대한 작업을 위해 N개의 멀티 스레드를 사용한다고 가정하면, 각 위젯이 그려지거나 업데이트 되는 순서를 보장할 수 없게 된다.
    • 단일 스레드 모델은 자원 접근에 대한 동기화를 신경쓰지 않아도 되고 작업전환(Context switching) 비용을 요구하지 않음으로 경합 상태와 교착 상태를 방지할 수 있다.
  • 안드로이드에서 단일 스레드 모델이란 안드로이드 화면을 구성하는 뷰나 뷰그룹을 하나의 스레드에서만 담당하는 원칙을 말한다.
  • 단일 스레드 모델의 규칙
      1. 메인스레드(UI 스레드)를 블럭하지 말 것
      1. 안드로이드 UI 툴킷은 오직 메인스레드(UI 스레드)에서만 접근할 수 있도록 할것
  • UI 작업은 단일 스레드 환경에서만 이뤄져야하며 UI 작업을 맡고 있는 메인스레드가 UI 작업을 할 땐, 단일 스레드 원칙에 맞게 동작해야 한다.
    • UI 작업은 메인스레드에서만 이뤄져야 한다. 메인스레드가 UI작업을 할 수 있는 유일한 스레드 인것 ️

메인 스레드에서 UI 작업을 수행하는 예시 코드

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val textView = findViewById<TextView>(R.id.textView)
        textView.text = "Hello, World from Main Thread!"
    }
}

📌 Looper 와 Handler 의 사용 목적

예를 들어 메인스레드, 다른 스레드 두 개 이상의 스레드가 동시에 같은 TextView에 setText()를 시도하는 경우
두 개의 스레드 중 setText()가 적용될 지 예측할 수 없고, 사용자는 둘 중 하나의 값만을 볼 수 있어 다른 한 스레드의 결과는 버려진다.
이와 같이 두 개 이상의 스레드를 사용할 때의 동기화 이슈를 차단하기 위해서 Looper 와 Handler 를 사용한다.

📌 Looper 와 Handler 작동 원리

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

📌 Handler

  • Handler 는 스레드의 Message Queue와 연계하여 MessageRunnable 객체를 받거나 처리하여 스레드 간의 통신을 할 수 있다.
  • Handler 객체는 하나의 스레드와 해당 스레드의 Message Queue에 종속된다.
  • 새로 Handler 객체를 만든 경우 이를 만든 스레드와 해당 스레드의 Message Queue에 바인드 된다.
  • 다른 스레드가 특정 스레드에게 메시지를 전달하려면 특정 스레드에 속한 HandlerpostsendMessage등의 메서드를 호출하면 된다.
  • 외부, 혹은 자기 스레드로부터 받은 메시지를 어떤 식으로 처리할 지는 handleMessage()메서드를 구현하여 정한다.
  • sendMessage()post()로 특정 Handler 에게 메세지를 전달할 수 있고, 재귀적인 호출도 가능하므로 딜레이를 이용한 타이머나 스케줄링 역할도 할 수 있다.

💡 전달 시점에 다른 메서드를 사용하여 Queue의 맨 위로 보내거나, 원하는 만큼 MessageRunnable 객체의 전송을 지연 시킬 수 있다.

리턴 메소드 인자 설명
void handleMessage Message msg LooperMessage Queue에서 꺼내준 MessageRunnable 객체를 처리.
(상속 시 구현 필수)
final boolean post Runnable r Message QueueRunnable r을 전달.
final boolean sendMessage Message msg Message QueueMessage msg을 전달.
final boolean postAtFrontOfQueue Runnable r Message Queue맨 앞Runnable r을 전달.
final boolean sendMessageAtFrontOfQueue Message msg Message Queue맨 앞Message msg을 전달.
final boolean postDelayed Runnable r, long delayMillis delayMillis 만큼 지연 후Message QueueRunnable r을 전달.
final boolean sendMessageDelayed Message msg, long delayMillis delayMillis 만큼 지연 후Message QueueMessage msg을 전달.

📌 Looper 와 Message Queue

  • Looper 는 무한히 루프를 돌며 자신이 속한 스레드의 Message Queue에 들어온 MessageRunnable 객체를 차례로 꺼내서 이를 처리할 Handelr에 전달하는 역할이다.
  • 메인스레드는 Looper가 기본적으로 생성돼 있지만, 새로 생성한 스레드는 기본적으로 Looper를 가지고 있지 않고 단지 run메서드만 실행한 후 종료하기 때문에 메시지를 받을 수 없다.
  • 기본 스레드에서 메세지를 전달받으려면 prepare() 메서드를 통해 Looper를 생성하고 loop()메서드를 통해 Looper가 무한히 루프를 돌며 Message Queue에 쌓인 MessageRunnable 객체를 꺼내 Handler에 전달하도록 한다.
  • 활성화된 Looper는 quit()quitSafely()메소드로 중단할 수 있다.
  • quit()메소드가 호출되면 Looper 는 즉시 종료되고, quitSafely() 메소드가 호출되면 현재 Message Queue에 쌓인 메세지들을 처리 한 후 종료한다.

📌 Message 와 Runnable

  • Message 란
    • 스레드 간 통신할 내용을 담는 객체이자 Queue에 들어갈 일감의 단위로 Handler를 통해 보낼 수 있다.
    • 일반적으로 Message 가 필요할 때 새 Message 객체를 생성하면 성능 이슈가 생길 수 있으므로 안드로이드가 시스템에 만들어 둔 Message Pool 객체를 재사용한다.
    • obtain() 메소드는 handler와 다른 인자들을 담은 Message 객체를 리턴한다.
  • Runnable 생성 방법
    • 새 스레드는 Thread() 생성자로 만들어서 내부적으로 run() 구현
    • Tread(Runnable runable)생성자로 만들어서 Runnable 인터페이스를 구현한 객체를 생성하여 전달.
      (Runnable로 스레드의 run()메서드를 분리 한것. 따라서 Runnable 인터페이스는 run() 추상 메서드를 가지고 있으므로 상속받는 클래스는 run() 코드를 반드시 구현해야한다.)

💡 Message가 intObject 같이 스레드간 통신할 내용을 담는다면, Runnable은 실행할 run()메소드와 그 내부에서 실행될 코드를 담는다는 차이가 있다.

📌 HandlerThread

  • HandlerThread 는 Looper를 기본으로 가지지 않는 불편함을 개선하기 위해 생성할 때 Looper를 자동으로 보유한 클래스이다.
  • HandlerThread 는 일반적인 스레드를 확장한 클래스로 내부에 반복해서 루프를 도는 Looper를 가진다.
  • 자동으로 Looper 내부의 Message Queue도 생성되므로 이를 통해 스레드로 MessageRunnable을 전달 받을 수 있다.

Looper 와 Handler 를 사용한 예시 코드

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvThread"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.widget.TextView
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity() {

    private lateinit var tvThread: TextView
    private lateinit var mainHandler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // UI 연결
        tvThread = findViewById(R.id.tvThread)

        // 메인 스레드의 Looper와 연결된 Handler 생성
        mainHandler = Handler(Looper.getMainLooper())

        // 첫 번째 스레드 시작
        startFirstThread()

        // 두 번째 스레드 시작
        startSecondThread()
    }

    private fun startFirstThread() {
        thread {
            // 1초 대기
            Thread.sleep(1000)

            // 메시지 생성
            val message = Message()
            message.what = 1 // 메시지 ID 설정
            message.obj = "Message from Thread 1" // 메시지 내용 설정

            // 메시지 처리를 메인 스레드로 전달
            mainHandler.post {
                handleThreadMessage(message)
            }
        }
    }

    private fun startSecondThread() {
        thread {
            // 3초 대기
            Thread.sleep(3000)

            // 메시지 생성
            val message = Message()
            message.what = 2 // 메시지 ID 설정
            message.obj = "Message from Thread 2" // 메시지 내용 설정

            // 메시지 처리를 메인 스레드로 전달
            mainHandler.post {
                handleThreadMessage(message)
            }
        }
    }

    private fun handleThreadMessage(message: Message) {
        // 메시지에 따라 TextView 업데이트
        when (message.what) { // 앞서 지정한 message 의 id 를 기준으로 판단
            1 -> tvThread.text = message.obj as String
            2 -> tvThread.append("\n${message.obj as String}")
        }
    }
}

 

 

참고 사이트

참고 사이트

참고 사이트

틀린부분이 있거나, 궁금하신게 있거나, 그냥 아무말이나 하고싶으면 댓글 남겨주세요 🥴

봐주셔서 감사합니다 🥰

반응형
LIST

'Android (이론)' 카테고리의 다른 글

[Android] Context  (0) 2024.06.06
[Android] Annotation  (0) 2024.05.05
[Android] ANR 이란?  (0) 2023.09.10
[ Kotlin ] Kotlin 과 Java 차이점 1 - 변수와 자료형  (0) 2022.01.18
[Android] 안드로이드 Intent란?  (0) 2022.01.04