getMainLooper() 함수
getMainLooper() 함수는 Main Thread(UI Thread)가 사용하는 Looper 즉 Main Looper를 반환합니다.
이 함수는 호출하는 스레드가 메인 스레드이거나 메인 스레드가 아니어도 언제든지 Main Looper를 반환합니다.
참고 1) Main thread의 Main Looper와 Handler는 ActivityThread에서 자동으로 생성하기 때문에 개발자가 명시적으로 생성하지 않습니다.
참고 2) Looper.myLooper() 함수는 호출한 스레드의 Looper를 반환합니다.
getMainLooper() 함수는 어떤 경우에 사용하면 될까요?
크게 3가지의 경우에 사용합니다.
1. Handler를 생성할 때 Main Looper를 생성자의 인자로 전달하고 싶을 경우
즉, 작업자 스레드(UI thread가 아닌 스레드)에서 UI thread에게 "Runnabel 작업" 또는 "메시지"을 보내고 싶을 때 사용할 수 있는 방법입니다.
View.post() 또는 runOnUiThread() API를 사용하는 것도 작업자 스레드에서 UI thread로 Runnable객체를 전달하는 용도로 사용됩니다.
public class MainActivity extends AppCompatActivity { private String TAG = "MainActivity"; private TestThread mTestThread; private Button mButton; static int count = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button)findViewById(R.id.button); } public void onClickButton(View view) { Log.d(TAG, "onClickButton"); mTestThread = new TestThread(); mTestThread.start(); } class TestThread extends Thread { private Handler mHandler; TestThread() { mHandler = new Handler(Looper.getMainLooper()) { // 핸들러에 Main Looper를 인자로 전달 @Override public void handleMessage(Message msg) { // 메인 스레드에서 호출 Log.d(TAG,"handleMessage : " + msg.what); switch(msg.what) { case 0: mButton.setText("Button 0"); break; case 1: mButton.setText("Button 1"); break; case 2: mButton.setText("Button 2"); count = 0; break; default: break; } } }; } @Override public void run() { Log.d(TAG, "Start TestThread"); Message msg = mHandler.obtainMessage(count++); mHandler.sendMessage(msg); // 메인 스레드로 메시지를 보냄 } } }
Handler를 생성할 때 인자로 Looper를 전달하면 어떤 과정을 거치는지 보도록 하겠습니다. (xref : Handler.java)
Main Looper를 인자로 넣으면 Main Looper의 Queue를 mQueue로 설정합니다. 이 부분이 중요합니다.
결론적으로 sendMessage() 또는 post()를 하게 되면 mQueue에 메시지나 작업이 들어가게됩니다. 즉 Main thread의 큐에 들어갑니다.
따라서 dequeue가 될때 Main thread에서 실행되는 것입니다.
실행 로그를 확인해 보겠습니다.
TestThread 스레드는(TID 12310) sendMessage() 함수를 호출하였고, 메인 스레드(TID 12235)에서 handleMessage() 함수가 호출되었습니다.
이 함수는 버튼을 터치할 때 전달 받은 msg.what 값에 setText("Button 0"), setText("Button 1"), setText("Button 2") 반복적으로 변경합니다.
01-06 14:05:30.519 12235-12235/com.example.codetravel.getmainlooper D/MainActivity: onClickButton 01-06 14:05:30.562 12235-12310/com.example.codetravel.getmainlooper D/MainActivity: Start TestThread 01-06 14:05:30.586 12235-12235/com.example.codetravel.getmainlooper D/MainActivity: handleMessage : 0 01-06 14:05:32.511 12235-12235/com.example.codetravel.getmainlooper D/MainActivity: onClickButton 01-06 14:05:32.537 12235-12335/com.example.codetravel.getmainlooper D/MainActivity: Start TestThread 01-06 14:05:32.571 12235-12235/com.example.codetravel.getmainlooper D/MainActivity: handleMessage : 1 01-06 14:05:34.660 12235-12235/com.example.codetravel.getmainlooper D/MainActivity: onClickButton 01-06 14:05:34.698 12235-12366/com.example.codetravel.getmainlooper D/MainActivity: Start TestThread 01-06 14:05:34.717 12235-12235/com.example.codetravel.getmainlooper D/MainActivity: handleMessage : 2 |
같은 원리로 다음 예제는 Runnable 작업을 메인 스레드로 전달하는 코드입니다.
public class MainActivity extends AppCompatActivity { private String TAG = "MainActivity"; private TestThread mTestThread; private Button mButton; static int count = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button)findViewById(R.id.button); } public void onClickButton(View view) { Log.d(TAG, "onClickButton"); mTestThread = new TestThread(); mTestThread.start(); } class TestThread extends Thread { @Override public void run() { Log.d(TAG, "Start TestThread"); Handler handler = new Handler(Looper.getMainLooper()); // 핸들러에 메인 루퍼를 인자로 전달 handler.post(new Runnable() { // 메인 스레드로 Runnable 객체를 보냄, runOnUiThread()함수 사용과 유사 @Override public void run() { // run()함수는 메인 스레드에서 실행 됨 Log.d(TAG, "Change Button text"); mButton.setText("Button changed"); } }); } } }
2. 현재 스레드의 루퍼가 Main Looper인지 아닌지 검사하고 싶을 경우
아래 예제에는 코드와 같이 버튼 UI 텍스트를 변경하는 changeButtonText() 함수가 있습니다.
이 함수를 호출하는 스레드의 Looper가 Main Looper 인지 아닌지에 따라서 버튼 UI 텍스트를 변경하는 방법을 다르게 하고 있습니다.
UI 변경은 UI thread에서만 허용하기 때문입니다.
만약 Main Looper라면 이것은 스레드가 Main 스레드를 의미하기 때문에 바로 setText() 함수를 호출 할 수 있습니다.
하지만 Main Looper가 아니라면 runOnUiThread() 또는 View.post() 함수 등을 사용해야 합니다.
public class MainActivity extends AppCompatActivity { private String TAG = "MainActivity"; private TestThread mTestThread; private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button)findViewById(R.id.button); } public void onClickButton(View view) { Log.d(TAG, "onClickButton"); changeButtonText(); mTestThread = new TestThread(); mTestThread.start(); } class TestThread extends Thread { @Override public void run() { Log.d(TAG, "Start TestThread"); changeButtonText(); } } public void changeButtonText() { Log.d(TAG, "changeButtonText myLooper() " + Looper.myLooper()); if (Looper.getMainLooper() == Looper.myLooper()) { // 현재 스레드의 루퍼와 메인 루퍼가 같은지 비교 mButton.setText("Button 1"); Log.d(TAG, "changeButtonText method is called from main thread"); } else { (MainActivity.this).runOnUiThread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } mButton.setText("Button 2"); Log.d(TAG, "changeButtonText method is called from non-main thread"); } }); } } }
실행 로그 입니다.
onClickButton에서 실행된 changeButtonText() 함수는 Main Looper를 사용하는 스레드에서 호출되었습니다.
그리고 별도로 생성한 스레드에서 호출된 changeButtonText() 함수는 당연히 Looper를 생성한 적이 없으므로 Looper.myLooper() 함수를 호출하면 null을 리턴합니다.
13:03:11.005 11277-11277/com.example.codetravel.getmainlooper D/MainActivity: onClickButton 01-06 13:03:11.005 11277-11277/com.example.codetravel.getmainlooper D/MainActivity: changeButtonText myLooper() Looper (main, tid 1) {f383015} 01-06 13:03:11.006 11277-11277/com.example.codetravel.getmainlooper D/MainActivity: changeButtonText method is called from main thread 01-06 13:03:11.008 11277-11364/com.example.codetravel.getmainlooper D/MainActivity: Start TestThread 01-06 13:03:11.008 11277-11364/com.example.codetravel.getmainlooper D/MainActivity: changeButtonText myLooper() null 01-06 13:03:14.034 11277-11277/com.example.codetravel.getmainlooper D/MainActivity: changeButtonText method is called from non-main thread |
Looper.myLooper() 함수를 로그로 출력하였을 때 내용은 아래와 같습니다.
Looper (main, tid 1) <== 메인 스레드에서 호출
null <== TestThread에서 호출
이것이 의미하는 것을 알기 위해서 Looper 클래스의 toString()함수를 보도록 하겠습니다. (xref : Looper.java)
Looper (" 스레드 이름", "스레드 ID") 임을 알 수 있습니다.
3. 현재 스레드가 Main thread(UI thread)인지 아닌지 검사하고 싶을 경우
public class MainActivity extends AppCompatActivity { private String TAG = "MainActivity"; private TestThread mTestThread; private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button)findViewById(R.id.button); } public void onClickButton(View view) { Log.d(TAG,"onClickButton()" + " " + Thread.currentThread() + " " + Looper.getMainLooper().getThread()); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { Log.d(TAG,"onClickButton() : This thread is main thread!"); } else { Log.d(TAG,"onClickButton() : This thread is not main thread!"); } mTestThread = new TestThread(); mTestThread.start(); } class TestThread extends Thread { @Override public void run() { Log.d(TAG,"TestThread run()" + " " + Thread.currentThread() + " " + Looper.getMainLooper().getThread()); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { Log.d(TAG,"TestThread run() : This thread is main thread!"); } else { Log.d(TAG,"TestThread run() : This thread is not main thread!"); } while(true) { // ps 명령어로 스레드 ID를 보기 위해서 스레드가 종료되지 않도록 하기 위한 코드 // 아무 동작도 안하고 그냥 살아 있는 스레드 } } } }
로그를 확인하기 전에 코드만으로 예측해보면 onClickButton() 함수는 UI thread에서 호출되기 때문에 "This thread is main thread!" 가 출력될 것이라는 것을 예측할 수 있습니다.
그리고 TestThread 스레드가 생성되어 run() 함수가 동작하는 스레드는 메인 스레드가 아닌 백그라운드 스레드입니다.
따라서 "This thread is not main thread!" 가 출력 될 것입니다.
실행 로그를 보도록 하겠습니다.
앞서 예측한 결과와 동일한 값이 출력 되었습니다.
01-06 06:10:49.537 32177-32177/com.example.codetravel.getmainlooper D/MainActivity: onClickButton() Thread[main,5,main] Thread[main,5,main] 01-06 06:10:49.537 32177-32177/com.example.codetravel.getmainlooper D/MainActivity: onClickButton() : This thread is main thread! 01-06 06:10:49.620 32177-822/com.example.codetravel.getmainlooper D/MainActivity: TestThread run() Thread[Thread-175,5,main] Thread[main,5,main] 01-06 06:10:49.620 32177-822/com.example.codetravel.getmainlooper D/MainActivity: TestThread run() : This thread is not main thread! |
ps 정보를 보면 아래와 같이 TestThread의 Name은 "Thread-175" 입니다.
root@generic_x86_64:/ # ps -t 32177 USER PID PPID VSIZE RSS WCHAN PC NAME u0_a68 32177 1264 1293420 48356 ep_poll 7fc1ba06b8ca S com.example.codetravel.getmainlooper // Main 스레드 u0_a68 32182 32177 1293420 48356 do_sigtime 7fc1ba06b7ca S Signal Catcher u0_a68 32183 32177 1293420 48356 poll_sched 7fc1ba06b60a S JDWP u0_a68 32184 32177 1293420 48356 futex_wait 7fc1ba000f68 S ReferenceQueueD u0_a68 32185 32177 1293420 48356 futex_wait 7fc1ba000f68 S FinalizerDaemon u0_a68 32186 32177 1293420 48356 futex_wait 7fc1ba000f68 S FinalizerWatchd u0_a68 32187 32177 1293420 48356 futex_wait 7fc1ba000f68 S HeapTaskDaemon u0_a68 32188 32177 1293420 48356 binder_thr 7fc1ba06bc67 S Binder_1 u0_a68 32189 32177 1293420 48356 binder_thr 7fc1ba06bc67 S Binder_2 u0_a68 32205 32177 1293420 48356 __skb_recv 7fc1ba06c30a S Thread-172 u0_a68 32214 32177 1293420 48356 ep_poll 7fc1ba06b8ca S RenderThread u0_a68 32231 32177 1293420 48356 futex_wait 7fc1ba000f68 S hwuiTask1 u0_a68 822 32177 1293420 48356 0 7fc1b11ec6cc R Thread-175 // TestThread 스레드 |
Thread.currentThread()와 Looper.getMainLooper.getThread() 함수는 모두 Thread 클래스 객체를 반환합니다.
출력 내용을 보면 아래와 같습니다.
Thread[main,5,main] <== main thread
Thread[Thread-175,5,main] <== TestThread
Thread 클래스 객체를 log로 출력했기 때문에 Thread 클래스의 toString() 함수가 호출되었습니다.
Thread["현재 스레드 이름", "우선순위", "스레드 그룹"] 형식으로 출력됩니다.
main 스레드와 TestThread 스레드는 이름은 다르지만 우선순위와 스레드 그룹은 동일하게 출력되었습니다.
별도로 우선순위와 스레드 그룹을 설정하지 않았기 때문에 default 상태의 값이 설정되어 있습니다.
이상으로 getMainLooper() 함수의 사용에 대해서 알아 보았습니다.
'Android > 기본 개념' 카테고리의 다른 글
Android 데이터 저장 방법 - Internal/External Storage (0) | 2018.01.28 |
---|---|
MediaRecorder 를 이용한 캠코딩 예제 (4) | 2018.01.11 |
View.post() 사용하기 (0) | 2017.12.30 |
MediaRecorder API 를 사용한 레코딩 예제 (0) | 2017.12.27 |
MediaRecorder API 를 사용한 레코딩 방법 (0) | 2017.12.20 |