-
[Android] Thread, Handler, Looper, Message Queue ๋ค๋ฃจ๊ธฐAndroid 2021. 11. 11. 03:13๋ฐ์ํ
Asynchronous Programming
์์ฆ ๋๋ถ๋ถ์ ํ๋ก๊ทธ๋จ๋ค์ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ์ด ๋ฉ๋๋ค. ์๋๋ก์ด๋์์๋ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ค์ํ ๋ฐฉ๋ฒ์ ์ด์ฉํ์ฌ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ๊ตฌํํ ์ ์์ต๋๋ค. ๋ณ๋์ Thread๋ฅผ ์์ฑํ์ฌ ๋์์ ์ผ๋ก ์์ ์ ์คํํ๊ฒ ํ๋ ๋ฐฉ๋ฒ๋ถํฐ AsyncTask, RxJava, RxKotlin, Coroutine ๋ฑ ํธ๋ฆฌํ๊ฒ ๊ตฌํํ ์ ์๋๋ก ๋ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ๋ํ๋๊ณ ์์ต๋๋ค. ํ์ง๋ง ์๋๋ก์ด๋์์ ์ ๊ณตํด์ฃผ๋ Thread, Handler, Looper, Message Queue๋ฅผ ์ ๋๋ก ์๊ณ ์์ด์ผ ๊ธฐ๋ณธ ์๋ฆฌ๋ฅผ ๊นจ๋ซ๊ฒ ๋๊ณ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ํธ๋ฆฌํจ์ ์ ์ ์์ ๊ฒ ๊ฐ์ ๊ณต๋ถ๋ฅผ ํ๊ฒ ๋์์ต๋๋ค.
Thread
์๋๋ก์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ผ์ ํ๋ก์ธ์ค๊ฐ ์คํ๋๊ณ ์๋์ผ๋ก ๋ฉ์ธ ์ค๋ ๋(Main Thread) ๊น์ง ์์ฑํ๊ณ ์คํํ๊ฒ ๋ฉ๋๋ค. Main Thread์์๋ UI ๊ด๋ จ๋ ์์ ๋ค์ ์ฒ๋ฆฌํ๊ฒ ๋๋๋ฐ, Main Thread ์ด์ธ์ ์ผ๋ฐ Thread์์๋ UI ์์ ์ ์ฒ๋ฆฌํ ์ ์๋๋ก ํ์์ต๋๋ค. ์๋ฅผ ๋ค์ด EditText์ ๊ฐ๋ค์ Thread1์์ ๋ณ๊ฒฝ ํ ๋ด์ฉ์ ์ฝ์ ๋ Thread2์์ ๋ค๋ฅธ ๋ด์ฉ์ผ๋ก ๋ณ๊ฒฝ์ ํ๋ฉด ๊ธฐ๋ํ ๊ฒฐ๊ณผ๊ฐ ๋ํ๋ ์ ์๊ณ ๊ฒฝ์์ํ(Race condition)๊ฐ ๋๊ธฐ ๋๋ฌธ์ ์๋๋ก์ด๋ ์์คํ ์์๋ Main Thread์์๋ง UI๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก ํ์์ต๋๋ค.
Main Thread
์ฃผ๋ก UI ์์ , UI ์ค๋ ๋๋ผ๊ณ ๋ ๋ช ์นญ, Main Thread์์ UI ์์ ์ด ์๋ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์ ํ๊ฒ ๋๋ฉด ANR์ด ๋ฐ์ํ ์๋ ์์. ์๋๋ก์ด๋ ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋์ ๊ทธ ๋ด๋ถ ํธ์ถ์ ๋ชจ๋ Main Thread์์ ์ฒ๋ฆฌ
Activity์ธ์๋ BroadcastReceiver, Service, Application ์ด UI์ ๊ด๋ จ์ด ์๋๋ผ๋ Main Thread์์ ์์ ์ฒ๋ฆฌ์ผ๋ฐ Thread
์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ํฅ์์ํค๊ธฐ ์ํ Thread ์์ฑ, Network ์์ , DB ์ฟผ๋ฆฌ ๋ฑ ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ๋ค์ ์ฃผ๋ก ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค. ์ผ๋ฐ Thread๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌํ๋ ์์ ๋ค์ด ๋ง์ ๋ฐฑ๊ทธ๋ผ์ด๋ Thread๋ผ๊ณ ๋ ๋ถ๋ฅธ๋ค.
โป ANR (Application Not Response)
์๋๋ก์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ํ๋ฉด์ ANR์ ๋ชจ๋๊ฐ ํ ๋ฒ์ฏค์ ๊ฒฝํํด๋ดค์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. ANR์ ๋ง ๊ทธ๋๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋์ด์ ๋ฐ์ํ์ง ์๋ ์ํ๋ก Main Thread์์ ๋๋ฌด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์ ์ํํ ๊ฒฝ์ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด ํฌ๊ทธ๋ผ์ด๋ ์ํ์์ ์์ ์งํ ์ค์ ANR์ด ๋ฐ์ํ๋ฉด ํ์ ์ด ๋ํ๋๊ณ ํด๋น ํ์ ์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ์ ์ข ๋ฃ ์ํฌ ์ ์์ต๋๋ค. ์๋๋ก์ด๋ ์ปดํฌ๋ํธ ์คํ์ Main Thread์์ ํ์ฌ ์ด๋์๋ ANR์ด ๋ฐ์ํ ์ ์์ง๋ง, ๋ํ์ ์ผ๋ก ์์ฃผ ๋ฐ์ํ๋ ์์์ ๋ํด ์์ฑํ์์ต๋๋ค.
- Main Thread์์ Network ์์ ๊ณผ ๊ฐ์ ์๊ฐ์ด ์ค๋ ์์๋๋ ์์ ์
- BroadcastReceiver๊ฐ 5์ด ์ด๋ด๋ก ๋ฐ์ํ์ง ์์ ์
- Service์์๋ ์ค๋ ์๊ฐ ๋์ ๋ฆฌํดํ์ง ์์ ๊ฒฝ์ฐ
์์ ์ค๋ช ๊ณผ ๊ฐ์ด UI๋ฅผ ๋ณ๊ฒฝํ๋ ์์ ์ Main Thread์์ ์ฒ๋ฆฌํ๊ณ ์๊ฐ์ด ์ค๋ ์์๋๋ ์์ ์ ์ผ๋ฐ Thread๊ฐ ๋ณ๋๋ก ์์ฑํ์ฌ ์ฒ๋ฆฌํ๋ฉด์ Thread ๊ฐ ํต์ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด์๋ ์์งํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํ Looper, Handler, Message Queue ๋ฑ ๋ค์ํ ํด๋์ค๊ฐ ์กด์ฌํ๊ณ ํด๋น ํด๋์ค์ ์ญํ ๊ณผ ํต์ ๊ณผ์ ์ ์ ์์์ผ ํฉ๋๋ค.
Looper
Looper ํด๋์ค๋ Message Queue๋ฅผ ๊ด๋ฆฌํ๋ ์ญํ ๋ก Message๋ Runnable ๊ฐ์ฒด๋ฅผ ํ๋์ฉ ๊บผ๋ด์ Handler์ ์ ๋ฌํฉ๋๋ค. Looperํด๋์ค์ Message Queue๋ก ์ธํด UI์ ๊ฒฝ์์ํ(race condition)์ ๋ฐฉ์งํ ์ ์์ต๋๋ค. Looper.loop() ๋ฉ์๋ ๋ด๋ถ์์ ๋ฌดํ๋ฃจํ๋ฅผ ๋๋ฉด์ Message Queue๋ฅผ ํ์ธํ์ฌ ํ๋์ฉ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); // Allow overriding a threshold with a system prop. e.g. // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start' final int thresholdOverride = SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow", 0); boolean slowDeliveryDetected = false; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ... try { msg.target.dispatchMessage(msg); if (observer != null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } } ... }
๋ฉ์๋๋ฅผ ์ข ๋ ์์ธํ ์ดํด๋ณด๋ฉด ์์ ์ Looper๋ฅผ ๊ฐ์ ธ์ค๊ณ , Looper ํด๋์ค์ ๋ฉค๋ฒ๋ณ์๋ก MessageQueue๊ฐ ์์ด์ ์ง์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. MessageQueue์์ ํ๋์ฉ ์ดํด๋ณด๋ฉด์ msg๊ฐ null์ด ์๋๋ฉด dispatchMessage ํจ์๋ฅผ ํตํด Message๋ฅผ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค. ์ฌ๊ธฐ์ Looper๊ฐ ์ข ๋ฃ๋ ๋ Message๊ฐ null์ด ๋ฉ๋๋ค.
Main Thread์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ ์์ Looper๋ฅผ ์์ฑํ์ฌ Looper.getMainLooper() ๋ฉ์๋๋ฅผ ํตํด ์ฝ๊ฒ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ํ์ง๋ง ์ผ๋ฐ Thread์์๋ Looper๊ฐ ์๊ธฐ ๋๋ฌธ์ run ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ์์ฑํ ์ฝ๋๋ง ์คํํ ๋ฟ ๋ฉ์ธ์ง๋ฅผ ๋ฐ๊ฑฐ๋ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์ผ๋ฐ Thread์์๋ ๋ค๋ฅธ Thread์์ ๋ณด๋ด๋ ๋ฉ์ธ์ง๋ฅผ ๋ฐ๊ฑฐ๋ ์ฒ๋ฆฌํ๊ธฐ ์ํด์๋ ์๋์ ๊ฐ์ ์ผ๋ จ์ ์์ ์ด ํ์ํฉ๋๋ค.
- ์ผ๋ฐ ์ค๋ ๋์์๋ ๋ฉ์์ง๋ฅผ ๋ฐ๊ธฐ ์ํด์๋ Looper๋ฅผ ์์ฑํฉ๋๋ค.
- Looper.prepare() ๋ฉ์๋๋ฅผ ํตํด ์ค๋ ๋๋ณ๋ก Looper๋ฅผ ์์ฑํฉ๋๋ค.
- ์ดํ Looper.loop() ๋ฉ์๋์์ MessageQueue๋ฅผ ํ๋์ฉ ํ์ธํ์ฌ Message๋ฅผ Handler์๊ฒ ์ ๋ฌํฉ๋๋ค.
- Handler๋ฅผ ํด๋น Message๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
- Looper๋ฅผ ์ข ๋ฃํ๊ธฐ ์ํด์ quit(), quitSafely() ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. quit ํจ์๋ ์ฆ์ ์ข ๋ฃํ๊ณ , quitSafely๋ ์์ ํ๊ฒ MessageQueue์ ์์ธ ๋ฉ์ธ์ง๊น์ง ์ฒ๋ฆฌํ ํ ์ข ๋ฃํ๊ฒ ๋ฉ๋๋ค.
์ฐธ๊ณ
https://developer.android.com/reference/android/os/Looper#quitSafely()
Message Queue
MessageQueue๋ Looper ํด๋์ค๋ฅผ ์ค๋ช ํ๋ฉด์ ๋ง์ด ์ค๋ช ํ์์ง๋ง ๋ค์ ์ค๋ช ํ์๋ฉด Message๋ฅผ ๋ด๋ Queue ํํ์ ์๋ฃ๊ตฌ์กฐ์ ๋๋ค. MessageQueue๋ Message๋ฅผ ์ฒ๋ฆฌํ ๋ Queue์์ ์ญ์ ํ๊ณ , Message๋ฅผ ์ถ๊ฐํ ๋๋ Queue์ ์ถ๊ฐํ๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ, ์ญ์ ๊ฐ ์ฉ์ดํ ๊ตฌ์กฐ์ฌ์ผ ํฉ๋๋ค. MessageQueue๋ LinkedBlockingQueue๋ก ๊ตฌํ๋์ด ์์ผ๋ฉฐ ๋งํฌ๋ ๋ฆฌ์คํธ์ ๊ฐ์ ๊ตฌ์กฐ๋ก ๋ค์ ๋ ธ๋์ ๋ํ ๋งํฌ๋ฅผ ๊ฐ์ง๋ ๋ฐฉ์์ผ๋ก ํ์ฌ Message ์ถ๊ฐ, ์ญ์ ๋ฅผ ๋น ๋ฅด๊ฒ ํ ์ ์์ต๋๋ค. MessageQueue์๋ ํด๋น Message๊ฐ ์คํ๋๋ ์์๋๋ก ์ถ๊ฐ๊ฐ ๋๋ฉฐ, ์ค๊ฐ์ ๋จผ์ ์ฒ๋ฆฌ๊ฐ ๋์ด์ผ ํ๋ Message๊ฐ ๋ค์ด์ค๋ฉด ์ค๊ฐ์ ์ฝ์ ์ด ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
์ฐธ๊ณ
https://developer.android.com/reference/android/os/MessageQueue
Handler
Handler๋ Looper๋ก ๋ถํฐ ์ฒ๋ฆฌ๋์ด์ผ ํ Message๋ค์ ๋ฐ์์ ์ฒ๋ฆฌํ๋ ์ญํ ๊ณผ, ๋ค์ Message Queue์ Message๋ฅผ ์ ๋ฌํ๋ ์ญํ ์ ํฉ๋๋ค. ๋ํ, Handler๋ฅผ ํตํด ๋ค๋ฅธ Thread์์ ์์ฒญํ๋ ๋ฉ์ธ์ง๋ฅผ ์ฒ๋ฆฌํ๊ณ ๋ค์ ํด๋น Thread์ ๋ฉ์ธ์ง๋ฅผ ์ ๋ฌํ ์๋ ์๊ธฐ ๋๋ฌธ์ Thread๊ฐ์ ํต์ ์ ๋ด๋นํ๋ค๊ณ ํ ์ ์์ต๋๋ค.
์๋๋ Handler์ ๊ธฐ๋ณธ ์์ฑ์๋ 4๊ฐ๋ก ์ ์๋์ด ์์์ง๋ง, ์ฌ๋ฌ๊ฐ์ง ๋ฒ๊ทธ๋ค๋ก ์ธํ์ฌ ์๋ 2๊ฐ์ ์์ฑ์ ์ฆ Looper๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๊ณ Looper์ Callback์ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๋ ์์ฑ์๋ง ์ฌ์ฉ๊ฐ๋ฅํฉ๋๋ค.
class LooperThread extends Thread{ public Handler mHandler; @Override public void run() { Looper.prepare(); mHandler = new Handler(Looper.myLooper()){ @Override public void handleMessage(@NonNull Message msg) { // ๋ฉ์ธ์ง ์ฒ๋ฆฌ super.handleMessage(msg); } }; Looper.loop(); } }
๋ค์ ์ฝ๋์์ Looper.prepare์ Looper.loop์ฝ๋๊ฐ ์์ผ๋ฉฐ NullPointerException(NPE)๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. Handler์ ์์ฑ์์๋ Looper๊ฐ ๋ค์ด๊ฐ์ผํ๊ณ myLooper๋ฅผ ํตํด์ TLS(Thread Local Storage)์ ์ ์ฅ๋ Looper๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ํ์ง๋ง prepare ํจ์๋ฅผ ํธ์ถํ์ง ์์ผ๋ฉด MessageQueue๊ฐ ์์ฑ๋์ง ์๊ธฐ ๋๋ฌธ์ null์ด ๋์ด ํด๋น ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ผ๋ฐ ์ค๋ ๋์์ Handler๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด Looper๋ฅผ ๋ฌด์กฐ๊ฑด ์์ฑํด์ผ ํฉ๋๋ค.
Handler์ ์ฃผ์ ์ฉ๋
1. ์ผ๋ฐ ์ค๋ ๋์์ UI ์ ๋ฐ์ดํธ
์ผ๋ฐ ์ค๋ ๋์์ ๋คํธ์ํฌ ํต์ ์ด๋ DB ์์ ์ UI ์ ๋ฐ์ดํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ Main Thread์ Handler๋ฅผ ํตํด ์ ๋ฐ์ดํธ ์์ ์ Runnable์ด๋ Message๋ฅผ ํตํด ์์ฒญํ ์ ์์
2. ๋ฉ์ธ ์ค๋ ๋์์ ๋ค์ ์์ ์์ฝ
๋ฉ์ธ ์ค๋ ๋์์ UI ์์ ์ ๋ฐ๋ก ํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ์ด ๋ API ์ค Delayed๊ฐ ๋ถ์ ๋ฉ์๋๋ฅผ ํตํด ํน์ ์๊ฐ ์ดํ์ ์คํํ ์ ์๋๋ก Handler๋ฅผ ์ฌ์ฉํ ์ ์์
3. ๋ฐ๋ณต ๊ฐฑ์
๋ฐ๋ณตํด์ UI๋ฅผ ๊ฐฑ์ ํ ๋ Handler์ Runnable ๊ฐ์ฒด๋ฅผ ๋์ ํ๊ณ Runnable ๊ฐ์ฒด์์๋ ํด๋น Handler๋ฅผ ๊ฐ์ง๊ณ ๋ค์ Runnable์ ํธ์ถํ๋ ์ฌ๊ท์ ์ธ ๋ฐฉ์์ผ๋ก ์ฝ๋ ์์ฑํฉ๋๋ค. ์ฒ์ Handler ํธ์ถ์ post ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ณ ์ดํ Runnable ๊ฐ์ฒด๋ postDelayed๋ฅผ ํธ์ถํ๋ฉด ๋ฐ๋ณต์ ์ผ๋ก ์ฃผ๊ธฐ๋ง๋ค Runnable ๊ฐ์ฒด๋ฅผ ์คํํฉ๋๋ค.
4. ์๊ฐ ์ ํ
์๋๋ก์ด๋์์ ์๊ฐ ์ ํํ ๋๋ Handler๋ฅผ ์ฃผ๋ก ์ฌ์ฉํฉ๋๋ค. ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ๋๋ธ ํด๋ฆญ ์ ํน์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ค๊ณ ํ์ ๋ ๋๋ธ ํด๋ฆญ์ ๋ํ ์๊ฐ์ ์ ํ์ ๋์ด์ ์๊ฐ ์ ํ์์ ํด๋ฆญ์ด๋ฒคํธ๊ฐ ํ ๋ฒ ๋ ์ ๋ ฅ์ด ๋๋ฉด ํน์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์ํ๋๋ก ํฉ๋๋ค.
ex) 2์ด ์์ ๋ค๋ก๊ฐ๊ธฐ ๋๋ธ ํด๋ฆญ ์ ์กํฐ๋นํฐ ์ข ๋ฃ, ์ฒ์ ํด๋ฆญ ์ finish ๋ณ์ true ๋ณ๊ฒฝ, ํ ๋ฒ ๋ ํด๋ฆญ ์ finish๊ฐ true๋ฉด ์ข ๋ฃ
ํ์ง๋ง 2์ด ์์ ๋๋ธ ํด๋ฆญ์ด ๋ฐ์ํ์ง ์์ผ๋ฉด Handler์ postDelayed๋ก ์ค์ ๋ ์ด๋ฒคํธ๊ฐ finish๋ฅผ false๋ก ๋ณ๊ฒฝ์ฐธ๊ณ
- ์๋๋ก์ด๋ ํ๋ก๊ทธ๋๋ฐ Next Step p21 ~ p34
- https://academy.realm.io/kr/posts/android-thread-looper-handler/
๋ฐ์ํ'Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] Context, ContextWrapper, ContextImpl ์ดํด (0) 2021.11.21 [Android] Preference, Shared Preference? (0) 2021.11.19 [Android] Activity๋? Activity LifeCycle ์๋ฒฝ ์ดํด (0) 2021.11.03 [Android] Drawble layer-list (0) 2021.08.15 [Android] Robelctric Test๋ (0) 2021.02.17