안드로이드 runOnUiThread() 메서드. (Android runOnUiThread() Method)

2019. 7. 8. 22:49


1. 안드로이드 핸들러. (Android Handler)

지난 글 [안드로이드 스레드 통신. 핸들러와 Runnable. (Android Thread Communication. Handler and Runnable.)]에서, 핸들러를 통해 Runnable 객체를 보내고, 객체에 담긴 run() 메서드가 수신 스레드에서 실행되는 과정을 살펴보았습니다.


Runnable 객체와 핸들러를 사용하여 스레드 통신을 수행하는 방법은, Message 객체를 사용할 때 요구되었던 메시지 식별 코드(what)나 파라미터(arg1, arg2), 또는 데이터 객체(obj)를 관리해야하는 번거로움을 덜어 줍니다. 그리고 핸들러의 handleMessage() 메서드를 작성하는 수고도 줄일 수 있어서, 비교적 간단하게 스레드 간 통신 수행할 수 있게 해줍니다. 물론 handlerMessage()에 들어갈 코드가 Runnablerun() 메서드에 일부 포함되어야 하는 건 어쩔 수 없긴 하지만 말이죠.


그런데 여기서, 한번 짚고 넘어가고 싶은 내용이 있습니다. 바로, 핸들러를 사용한 스레드 통신이 "어떤 상황에 가장 많이 사용되는가"라는 것입니다.


안드로이드 앱을 만들 때, 가장 조심스럽게 다루어져야 하는 스레드는 메인 스레드(Main Thread)입니다. [안드로이드 스레드(Android Thread) - 3. 안드로이드 앱의 메인 스레드(Main Thread)]에서 설명했듯이, 안드로이드 앱의 시작점이자, 사용자 입력과 UI를 책임지는 스레드, 바로 메인 스레드죠. 그래서 앱이 실행될 때 메인 스레드와 병행적으로 처리되어야 하는 작업들은 새로운 스레드에서 실행하고, 그 결과(또는 코드)를 핸들러를 통해 메인 스레드로 전달한 다음 화면에 반영하는 것입니다.


자, 이제 "핸들러를 통한 스레드 통신이 가장 많이 사용되는 경우"가 예상되나요? 바로 새로운 스레드에서 실행된 결과를 메인 스레드를 통해 화면에 표시하고자 할 때입니다.

2. 핸들러(Handler)와 Runnable 되짚어보기.

핸들러를 통해 Runnable 객체를 보내는 과정을 다시 한번 정리해보겠습니다.


  1. 수신 측 스레드에서 핸들러 객체를 생성.
  2. 송신 측 스레드에서 Runnable 객체 생성 및 run() 메서드 구현.
  3. Handler.post() 메서드를 통해 Runnable 객체를 전달.


비교적 간단한 절차를 통해 Runnable 객체를 보낼 수 있다는 것을 확인할 수 있는데요. 이 절차에서, 코드 작성의 수고를 조금이라도 줄일 수 있는 방법을 한번 고민해 보죠. 특히, Runnable 객체의 수신 스레드가 UI를 관리하는 메인 스레드이고, 핸들러에 대한 참조를 Activity가 가지는 경우를 말입니다.

2.1 액티비티에서 Runnable 수신하는 경우.

앞에서, 핸들러를 통한 스레드 통신이 가장 많이 사용되는 경우가 "스레드 실행 결과를 화면에 표시하고자 할 때"라고 언급했습니다. 그래서 화면 구성의 가장 기본이 되는 요소인 액티비티에서 핸들러 객체를 생성하게 되는데요.


그렇다면, Runnable 객체를 보낼 때마다 핸들러 객체를 새로 만들지 말고, 액비비티가 기본적으로 가지고 있는 핸들러를 사용하면 되지 않을까요? 그리고 그 핸들러를 통해 post() 메서드를 호출하게 만들면, 직접 Handler.post() 메서드를 호출하는 수고를 줄일 수 있을 것입니다. 그리고 이러한 작업을, 액티비티에 새로운 메서드를 추가해서 구현하면 되겠네요.


아참, Runnable 객체를, 다른 스레드가 아닌 메인 스레드 자신이 보낼 수도 있으니깐, 이 때는 불필요하게 post() 메서드를 호출하지 말고, 그냥 Runnable 객체에 구현된 run() 메서드를 바로 실행하도록 만드는 게 더 좋겠네요. 아래와 같이, pseudo-code로 구현해본 sendRunnable() 메서드처럼 말이죠.


public class MainActivity extends ... {

    public final void sendRunnable(Runnable r) {
        if (메서드가 새로운 스레드에서 실행된다면) {
            핸들러의 post() 메서드를 통해 r 전달.
        } else {
            r.run() 실행.
        }
    }
}


자, 어떤가요? 위에서 액티비티에 sendRunnable() 메서드를 구현해놓았으니, 앞으로 매번 핸들러를 만들고 post() 메서드를 호출할 필요가 없습니다. 그냥 전달할 Runnable 객체를 sendRunnable() 메서드로 넘기기만 하면 되는 것입니다.


    sendRunnable(new Runnable() {
        @Override
        public void run() {
            // TODO.
        }
    }) ;


음, 그런데 잠깐, 왠지 이런 작업을 하는 메서드가 Activity 클래스에 이미 만들어져 있을 것 같은데요?


네. 바로 runOnUiThread() 입니다.

3. 액티비티의 runOnUiThread() 메서드.

액티비티의 runOnUiThread() 메서드


runOnUiThread() 메서드는 Activity 클래스에서 제공되는 메서드입니다. 앞에서 임의로 만들어본, sendRunnable() 메서드의 기능이 고스란히 구현된 메서드입니다. 즉, 개발자가 만든 Runnable 객체를 메인 스레드에서 실행되도록 만드는 메서드인데, 현재 스레드가 메인 스레드인지 여부를 검사하여 메인 스레드가 아니라면 post() 메서드를 실행하고, 메인 스레드라면 Runnablerun() 메서드를 직접 실행합니다.


runOnUiThread() 메서드는 아래와 같이 간단하게 구현되어 있습니다.


    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

runOnUiThread() 메서드를 한 문장으로 요약하자면, "Runnable 객체에 구현된 코드를 반드시 메인 스레드에서 실행해야 할 때 사용하는 메서드"입니다.

4. runOnUiThread() 사용 예제.

runOnUiThread() 메서드에 대해 알아보았으니, 예제를 통해 runOnUiThread() 메서드를 어떻게 사용하는지 알아보겠습니다.


작성할 예제는 [안드로이드 스레드 예제. 스레드로 고민해보기. (Android Thread UseCase) - 5. 스레드 적용. 스레드 간 통신 적용하기.]와 [안드로이드 스레드 통신. 핸들러와 Runnable - 4. 핸들러(handler)와 Runnable을 사용한 스레드 통신 예제]에서 작성했던 "디지털 시계" 예제와 동일합니다.

4.1 메인액티비티 레이아웃 작성.

메인액티비티 화면에는 아래 그림과 같이 화면 가운데 현재 시각을 표시할 수 있는 텍스트뷰를 배치합니다.


[STEP-1] 메인액티비티 레이아웃 작성.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="48sp"
        android:text="00:00:00"
        android:id="@+id/clock"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

4.2 runOnUiThread() 메서드로 Runnable 객체 전달 기능 구현.

자, 이제 Runnable 객체의 run() 메서드에 현재 시각을 표시하는 코드를 작성하고, 해당 Runnable 객체를 runOnUiThread() 메서드에 전달하는 코드를 작성합니다.


[STEP-2] Runnable 객체에 현재 시각 갱신 코드 작성 후, 메인 스레드로 전달.
public class MainActivity extends AppCompatActivity {

    TextView clockTextView ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... 코드 계속.

        // 핸들러로 전달할 runnable 객체. 수신 스레드 실행.
        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Calendar cal = Calendar.getInstance();

                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                String strTime = sdf.format(cal.getTime());

                clockTextView = findViewById(R.id.clock);
                clockTextView.setText(strTime);
            }
        } ;

        // 새로운 스레드 실행 코드. 1초 단위로 현재 시각 표시 요청.
        class NewRunnable implements Runnable {
            @Override
            public void run() {
                while (true) {

                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace() ;
                    }

                    // 메인 스레드에 runnable 전달.
                    runOnUiThread(runnable) ;
                }
            }
        }

        NewRunnable nr = new NewRunnable() ;
        Thread t = new Thread(nr) ;
        t.start() ;
    }
}

4.3 실행 결과.

예제 작성을 완료하고 실행하면, 현재 시각이 1초마다 갱신되는 것을 확인할 수 있습니다.


5. 참고.

.END.


ANDROID 프로그래밍/THREAD , ,

  1. Blog Icon
    초보 개발자 1호

    뽀따님 덕분에 쓰레드에 대해 요즘이나마 조금씩 이해해가는 것 같습니다. 감사합니다.

  2. 제 글을 읽는 분들이 스레드에 대해 조금이라도 쉽게 이해하실 수 있도록, 최대한 글을 쉽게 쓰려고 노력했는데, 도움이 된 것 같아서 다행이네요.

    댓글 남겨주셔서 감사합니다.

  3. Blog Icon
    사랑합니다

    안녕하세요 빛따님. 오늘도 질문을 드리러 왔습니다.
    러너블 객체는 메시지가 아니라 러너블 객체자체를 넘겨 안에 있는 코드를 실행하게 하는 걸로 알고있습니다!
    근데, 이 경우, 예제처럼 텍스트뷰의 setText를 바꾼다던가, 이런식의 메인 UI스레드가 필요한 작업의 경우는
    해당 러너블 객체를 따로 자바 파일로 만들어서 구현할 수 없는건가요?
    다른 클래스에서 구현하려면, 다른 클래스에서 메시지를 보내게 하고, 메인 스레드가 그 메시지를 받아서 UI를 변경하는 수밖에 없나요?
    한마디로 질문을 요약하면.. 다른 자바파일로 구현된 러너블의 run()내용에서는 UI를 변경할 수 없으니 UI를 변경하려면, 러너블이 아니라 기존의 메세지를 보내는 식으로 해서
    변경하는측이 핸들러 메시지를 받고, 그 액티비티에서만 변경을 할 수 있나요?

  4. 다른 자바 파일에서 구현할 수 있습니다.

    다른 자바 파일에 구현된 Runnable 객체에서 UI를 변경하려는 액티비티를 참조할 수 있게만 만들면 해당 액티비티 참조를 통해 UI를 변경할 수 있습니다.

    감사합니다.

  5. Blog Icon
    뱁새

    안녕하세요,
    게시글이 게시된지 꽤 오래되어서 보실지 모르겠지만 일단 질문 남겨봅니다 ㅠㅠ

    "Runnable 객체를, 다른 스레드가 아닌 메인 스레드 자신이 보낼 수도 있으니깐, 이 때는 불필요하게 post() 메서드를 호출하지 말고, 그냥 Runnable 객체에 구현된 run() 메서드를 바로 실행하도록 만드는 게 더 좋겠네요."

    라고 하셨는데요,
    메인 스레드 자신이 보내더라도, 그걸 바로 run 하면 안되고 MessageQueue에 들어가서 순서에 맞게 실행되어야 하는 거 아닌가요? 이를 위해서 항상 post를 써야 하는 것 아닌가요?



    질문과는 별개로, 안드로이드 공부하는 중인데 정말 이 정도 양질의 한글 자료가 있음에 감동받았습니다 ㅠㅠㅠ
    정말 감사드립니다.

  6. 메인 스레드가 자신에게 Runnable 객체를 보낼 때, 반드시 MessageQueue를 통해서 순서에 맞게 실행되어야 하는 건 아닙니다.

    일반적으로 멀티스레드 환경에서 동작하는 프로그램을 작성할 때는 "순서"가 반드시 일정하리라는 법이 없습니다.

    즉, 메인 스레드로 전달되는 이벤트가 일정한 순서로 전달되어야 한다는 규칙이 없으며, 일정한 순서대로 전달된다고 가정해서도 안됩니다.

    그러므로 항상 post를 사용할 이유도 없습니다. 순서가 일정하지 않은데, MessageQueue를 통해 순서대로 처리하도록 만들이유도 없는 것이지요.

    그런데 만약, 어떤 이벤트들이 반드시 순서대로 실행되어야 한다면, 그리고 그것을 메인 스레드에서 감지할 수 있고, 제어할 수 있다면 말씀하신대로 post를 통해 순서대로 실행되도록 만들어야겠죠.

    하지만, 다시 한번 말씀드리지만, 멀티 스레드 환경에서 어떠한 이벤트들이 반드시 순서대로 실행된다고 가정하여 코드를 작성하는 것은 좋지 않은 방법입니다.

    감사합니다.

  7. Blog Icon
    뱁새

    그렇군요 친절한 답변 정말 감사합니다!
    뽀따님 블로그 덕분에 공부 정말 많이 되네요!!

  8. 도움이 되어서 다행이네요.
    방문해 주셔서 감사합니다.

  9. Blog Icon
    sgo8308

    안녕하세요
    안드로이드 공식 문서를 보면 runOnUiThread는 runnable 객체를 메인 쓰레드에 event queue에 보낸다고 되어 있는데 이 event queue는 message queue와는 다른 것인가요?
    그럼 runOnUiThread로 보낸 Runnable객체는 message queue로 들어가는 것이 아닌가요?

  10. 안드로이드에서 event queue와 message queue는 같은 의미로 해석하시면 됩니다.

    감사합니다.