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() 함수의 사용에 대해서 알아 보았습니다.


앞서 안드로이드 "안드로이드 메인 스레드 포스팅"을 통하여 메인스레드의 특징에 대해서 알아보았습니다. 

그 중에서 메인 스레드가 아닌 스레드에서 Button UI를 조작하다가 CalledFromWrongThreadException를 경험하기도 하였습니다.

이런 문제를 해결할 수 있는 방법에 대해서 알아보겠습니다.


Activity.runOnUiThread(Runnable) 사용

Developer Android 사이트에서 찾아보면 다음과 같이 설명이 있습니다.

설명 중 다음 부분이 우리가 처한 상황을 설명해 주고 있습니다.

"특정 동작을 UI 스레드에서 동작하도록 합니다. 만약 현재 스레드가 UI 스레드이면 그 동작은 즉시 수행됩니다."

하지만 "현재 스레드가 UI 스레드가 아니면, 필요한 동작을 UI 스레드의 이벤트 큐로 전달한다" <== 이부분 입니다.


우선 설명을 믿고 코드를 만들어 보겠습니다. 먼저 CalledFromWrongThreadException를 발생시켰던 코드는 아래와 같습니다.

 
    public class MainActivity extends Activity {
    private String TAG = "AndroidThread";
    private Button mDownloadButton;
    private Button mCancelButton;
    private Download mDownload;
    private boolean isDownloading = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDownloadButton = (Button)findViewById(R.id.button_download);
        mCancelButton = (Button)findViewById(R.id.button_cancel);
    }

    public void onDownload(View view) {
        Log.d(TAG, "Press Download button");
        isDownloading = true;
        mDownload = new Download();
        mDownload.start();
    }

    public void onCancel(View view) {
        Log.d(TAG, "Press Cancel button");
        isDownloading = false;
    }

    public class Download extends Thread {

        @Override
        public void run() {
            for(int i=1;i<=10;i++) {

                if (!isDownloading) {
                    break;
                }
                try {
                    Thread.sleep(1000);
                    Log.d(TAG, "Downloading ..." + i*10 + "%");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(i == 10) {
                    mDownloadButton.setText("Downloaded"); // CalledFromWrongThreadException 유발 코드!!
                }
            }
            isDownloading = false;
        }
    }
}

public class MainActivity extends Activity {
    private String TAG = "AndroidThread";
    private Button mDownloadButton;
    private Button mCancelButton;
    private Download mDownload;
    private boolean isDownloading = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDownloadButton = (Button)findViewById(R.id.button_download);
        mCancelButton = (Button)findViewById(R.id.button_cancel);
    }

    public void onDownload(View view) {
        Log.d(TAG, "Press Download button");
        isDownloading = true;
        mDownload = new Download();
        mDownload.start();
    }

    public void onCancel(View view) {
        Log.d(TAG, "Press Cancel button");
        isDownloading = false;
    }

    public class Download extends Thread {

        @Override
        public void run() {
            for(int i=1;i<=10;i++) {

                if (!isDownloading) {
                    break;
                }
                try {
                    Thread.sleep(1000);
                    Log.d(TAG, "Downloading ..." + i*10 + "%");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(i == 10) {

                    (MainActivity.this).runOnUiThread(new Runnable(){

                        @Override
                        public void run() {
                            Log.d(TAG, "Change Button text");
                            mDownloadButton.setText("Downloaded");
                        }
                    });
                }
            }

            isDownloading = false;
        }
    }
}


다음은 runOnUiThread 함수를 사용한 코드입니다.

public class MainActivity extends Activity {
    private String TAG = "AndroidThread";
    private Button mDownloadButton;
    private Button mCancelButton;
    private Download mDownload;
    private boolean isDownloading = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDownloadButton = (Button)findViewById(R.id.button_download);
        mCancelButton = (Button)findViewById(R.id.button_cancel);
    }

    public void onDownload(View view) {
        Log.d(TAG, "Press Download button");
        isDownloading = true;
        mDownload = new Download();
        mDownload.start();
    }

    public void onCancel(View view) {
        Log.d(TAG, "Press Cancel button");
        isDownloading = false;
    }

    public class Download extends Thread {

        @Override
        public void run() {
            for(int i=1;i<=10;i++) {

                if (!isDownloading) {
                    break;
                }
                try {
                    Thread.sleep(1000);
                    Log.d(TAG, "Downloading ..." + i*10 + "%");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(i == 10) {
                       (MainActivity.this).runOnUiThread(new Runnable(){

                        @Override
                        public void run() {
                            Log.d(TAG, "Change Button text");
                            mDownloadButton.setText("Downloaded");
                        }
                    });
                }
            }

            isDownloading = false;
        }
    }
}


run()함수를 오버라이드하여 Runnable 인터페이스를 구현하였습니다. 

이제 run() 함수에 있는 작업은(여기서는 setText함수 실행이겠죠) main thread(UI thread) 에서 실행 됩니다. 

로그로 확인 해보겠습니다.   

12-08 16:05:22.968 3443-3443/com.example.codetravel.androidthread D/AndroidThread: Press Download button

12-08 16:05:23.986 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...10%

12-08 16:05:24.987 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...20%

12-08 16:05:25.990 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...30%

12-08 16:05:26.994 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...40%

12-08 16:05:27.998 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...50%

12-08 16:05:29.000 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...60%

12-08 16:05:30.003 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...70%

12-08 16:05:31.006 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...80%

12-08 16:05:32.011 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...90%

12-08 16:05:33.013 3443-3467/com.example.codetravel.androidthread D/AndroidThread: Downloading ...100%

12-08 16:05:33.014 3443-3443/com.example.codetravel.androidthread D/AndroidThread: Change Button text 

다운로드 진행 상태를 출력하는 로그는 TID3467 thread에서 동작합니다. 

의도한대로 다운로드 완료 후,  setText 함수를 수행하는 run 함수가 UI thread에서 실행된 것을 확인 할 수 있습니다.

다음 그림은 다운로드 버튼의 텍스트가 정상적으로 변경된 모습입니다.



runOnUiThread 함수는 어떻게 이런 일을 할까요? xref에서 runOnUiThread 함수를 살펴 보겠습니다.

"특정 동작을 UI 스레드에서 동작하도록 합니다. 만약 현재 스레드가 UI 스레드이면 그 동작은 즉시 수행됩니다."

하지만 "현재 스레드가 UI 스레드가 아니면, 필요한 동작을 UI 스레드의 이벤트 큐로 전달합니다 설명이 그대로 코드화 되어 있네요


runOnUiThread가 실행된 스레드는 TID3467이므로 즉 UI thread가 아니므로 mHandler.post(action) 코드가 실행됩니다.

mHandler.post(action)을 실행하면 아래와 같이 순서대로 함수가 호출됩니다.(1 -4)

결과적으로 Runnable r은 UI thread의 queue에 메시지 형식으로 enqueue가 됩니다. 

후에 queue에 있는 메세지를 꺼내서 UI thread에서 동작을 수행할 것입니다.

1. post(Runnable r)

2. sendMessageDelayed(Message msg, long delayMillis)

3. sendMessageAtTime(Message msg, long uptimeMillis)

 - MessageQueue queue = mQueue;

4. enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)


간략하게 말씀드리면 처음 startActivity() 함수를 통해서 MainActivity가 생성되는 과정은 zygoteInit에 의해서 ActivityThread가 생성됩니다.

ActivityThread는 아래와 같이 Main Looper와 Handler를 생성합니다. 

 // frameworks/base/core/java/android/app/ActivityThread.java

public final class ActivityThread {

...

public static void main(String[] args) { 

...

Looper.prepareMainLooper(); // 1. UI thread의 루퍼를 생성 

ActivityThread thread = new ActivityThread(); 

thread.attach();


if(sMainThreadHandler == null) {     

    sMainThreadHandler = thread.getHandler(); // 2. 핸들러 설정

 } 

Looper.loop(); // 3. 루퍼 동작 시작

}

}


이것이 안드로이드 시스템에서 자동으로 생성되는 UI thread의  Looper와  Handler 입니다. 

그리고 ActivityThread는 ActivityManagerService 와 바인더 통신을 하여 MainActivity를 생성하게 됩니다. 

따라서 ActivityThread가 생성한 MainActivity는 ActivityThread에서 생성한 Looper와 Handler를 사용하게 됩니다.

즉 위의 3번에서 mQueue는 UI thread의 Looper가 관리하는 queue를 가리킵니다.

결국 queue에 메세지 형태로 들어간 Runnable 작업은 UI Thread에서 dequeue 되어 UI Thread상에서 수행됩니다.

 UI thread에서 Button의 Text 변경 작업을 했기 때문에 CalledFromWrongThreadException와 같은 문제가 발생하지 않는 것입니다.


아래 콜스택은 MainActivity 액티비티의 onCreate가 불리는 함수 호출 과정입니다.

"main@4450" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at com.example.codetravel.androidthread.MainActivity.onCreate(MainActivity.java:17)
      at android.app.Activity.performCreate(Activity.java:6757)
      at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2703)
      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2811)
      at android.app.ActivityThread.-wrap12(ActivityThread.java:-1)
      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1528)
      at android.os.Handler.dispatchMessage(Handler.java:102)
      at android.os.Looper.loop(Looper.java:154)
      at android.app.ActivityThread.main(ActivityThread.java:6316)
      at java.lang.reflect.Method.invoke(Method.java:-1)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:872)

      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:762) 


ActivityThread의 main 함수에서 실행된 Looper.loop() 함수에 의해서 메세지가 처리되고 있는것을 볼 수 있습니다.

그리고 MainActivity가 생성되면서 onCreate가 호출됩니다.

위의 과정은 붉은색으로 표시된 "main"이라는 표시로 main thread 즉 UI thread의  콜스택임을 알 수 있습니다.  

이상으로 runOnUiThread 함수의 사용에 대해서 간략하게 보았습니다.






+ Recent posts