아무고토 몰라효
[Android] MainThread & Handler 본문
반응형
👼🏻 초보 안드로이드 개발자가 매번 구글링하기 싫어서 정리하는 블로그 👼🏻
안녕하세요! 🙋🏻♀️
Main Thread 와 Handler 에 대해 작성해보았습니다.
Main Thread + Handler
📌 일반적인 Main Thread
- 프로세스 실행 중의 필요에 따라
Thread가 생성 및 실행된다. Thread는 기존에 이미 실행되어있는 다른Thread에 의해 생성 및 실행한다.- 최초의
Thread를Main Thread라고 부른다. 이Thread가 생성되고 시작되는 곳을main()함수라고 부른다. - 프로세스가 시작되어 프로세스의 시작점인
main()함수에서 실행되는 최초의Thread가Main 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가 애플리케이션의 메인 클래스다. ActivityThread의main()메서드가 애플리케이션의 시작 지점이다.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) 비용을 요구하지 않음으로 경합 상태와 교착 상태를 방지할 수 있다.
- 안드로이드에서 단일 스레드 모델이란 안드로이드 화면을 구성하는 뷰나 뷰그룹을 하나의 스레드에서만 담당하는 원칙을 말한다.
- 단일 스레드 모델의 규칙
-
- 메인스레드(UI 스레드)를 블럭하지 말 것
- 안드로이드 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에서Message나Runnable객체를 차례로 꺼내Handler가 처리하도록 전달한다. Handler는 Looper 로 부터 받은Message를 실행, 처리하거나 다른 스레드로부터 메세지를 받아Message Queue에 넣는 역할을 한다.
📌 Handler
- Handler 는 스레드의
Message Queue와 연계하여Message나Runnable객체를 받거나 처리하여 스레드 간의 통신을 할 수 있다. - Handler 객체는 하나의 스레드와 해당 스레드의
Message Queue에 종속된다. - 새로 Handler 객체를 만든 경우 이를 만든 스레드와 해당 스레드의
Message Queue에 바인드 된다. - 다른 스레드가 특정 스레드에게 메시지를 전달하려면 특정 스레드에 속한
Handler의post나sendMessage등의 메서드를 호출하면 된다. - 외부, 혹은 자기 스레드로부터 받은 메시지를 어떤 식으로 처리할 지는
handleMessage()메서드를 구현하여 정한다. sendMessage()나post()로 특정 Handler 에게 메세지를 전달할 수 있고, 재귀적인 호출도 가능하므로 딜레이를 이용한 타이머나 스케줄링 역할도 할 수 있다.
💡 전달 시점에 다른 메서드를 사용하여 Queue의 맨 위로 보내거나, 원하는 만큼 Message나 Runnable 객체의 전송을 지연 시킬 수 있다.
| 리턴 | 메소드 | 인자 | 설명 |
| void | handleMessage | Message msg | Looper가 Message Queue에서 꺼내준 Message나 Runnable 객체를 처리. (상속 시 구현 필수) |
| final boolean | post | Runnable r | Message Queue 에 Runnable r을 전달. |
| final boolean | sendMessage | Message msg | Message Queue 에 Message 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 Queue 에 Runnable r을 전달. |
| final boolean | sendMessageDelayed | Message msg, long delayMillis | delayMillis 만큼 지연 후Message Queue 에 Message msg을 전달. |
📌 Looper 와 Message Queue
- Looper 는 무한히 루프를 돌며 자신이 속한 스레드의
Message Queue에 들어온Message나Runnable객체를 차례로 꺼내서 이를 처리할Handelr에 전달하는 역할이다. - 메인스레드는 Looper가 기본적으로 생성돼 있지만, 새로 생성한 스레드는 기본적으로 Looper를 가지고 있지 않고 단지
run메서드만 실행한 후 종료하기 때문에 메시지를 받을 수 없다. - 기본 스레드에서 메세지를 전달받으려면
prepare()메서드를 통해 Looper를 생성하고loop()메서드를 통해 Looper가 무한히 루프를 돌며Message Queue에 쌓인Message나Runnable객체를 꺼내 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가 int 나 Object 같이 스레드간 통신할 내용을 담는다면, Runnable은 실행할 run()메소드와 그 내부에서 실행될 코드를 담는다는 차이가 있다.
📌 HandlerThread
- HandlerThread 는 Looper를 기본으로 가지지 않는 불편함을 개선하기 위해 생성할 때 Looper를 자동으로 보유한 클래스이다.
- HandlerThread 는 일반적인 스레드를 확장한 클래스로 내부에 반복해서 루프를 도는 Looper를 가진다.
- 자동으로 Looper 내부의 Message Queue도 생성되므로 이를 통해 스레드로
Message나Runnable을 전달 받을 수 있다.
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}")
}
}
}
틀린부분이 있거나, 궁금하신게 있거나, 그냥 아무말이나 하고싶으면 댓글 남겨주세요 🥴
봐주셔서 감사합니다 🥰
반응형
'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 |
Comments