ANDROID 프로그래밍/THREAD

안드로이드 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() 메서드는 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.