안드로이드는 UI 관련 처리를 단일 thread 모델로 처리하고 있다. 즉, UI만 처리하는 thread가 모든 UI처리를 담당하며 그 외의 worker thread들은 UI에 직접 접근할 수 없다. 관례적으로 UI 처리 쓰레드를 UI thread 혹은 main thread라고 부른다.

worker thread에서 UI에 접근하기 위해서는 UI thread에 작업을 요청해야 한다. worker thread가 몇이나 될진 알 수 없으므로 이런 작업에는 queueing이 매우 적절하다. 따라서 UI thread는 기본적으로 MessageQueue를 포함하고 있는 HandlerThread이다.

안드로이드에서는 UI thread에 접근할 수 있는 몇가지 방법을 제공한다. 크게 3가지로 정리해볼 수 있다.

  1. HandlerLooper.getMainLooper()를 이용한 방법
  2. Activity.runOnUiThread를 이용한 방법
  3. View.post를 이용한 방법

MainLooper를 조작하는 Handler

UI thread는 앞에서 말했듯이 HandlerThread이며, 자체적으로 MessageQueue와  Looper를 가지고 있다. 이를 이용한 가장 기본적인 UI thread 조작법으로는 Handler를 예로 들 수 있겠다.

Handler는 Looper를 조작(handling)할 수 있는 클래스이다. 이를테면 메시지나 콜백(Runnable)을 큐에 집어넣고 post한 순서대로 이를 UI thread가 핸들링 하도록 만들 수 있다. MessageQueue가 empty가 되면 블록되며 사용자가 message를 보내거나 콜백을 post 할 때까지 대기한다.

아래에 설명하는 것들은 모두 Handler를 기반으로 응용한 것이다.

Activity.runOnUiThread()

Activity의 runOnUiThread는 Handler를 응용한 메서드로, 액티비티에서 UI thread 실행이 필요한 경우 따로 Handler 선언 없이도 사용할 수 있는 유용한 메서드이다.

내부구조는 아래와 같다. (android-26 SDK 기준)

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

현재 쓰레드가 UI thread면 직접 Runnable을 호출해서 실행하고, 그렇지 않으면 액티비티가 소유한 Handler에게 post를 요청한다. 이유는 매우 자명하다. 어차피 같은 쓰레드면 큐에 넣어서 대기를 탈 필요가 없다.

View.post()

겉보기에는 Handler와 별 차이가 없어보인다. 그래서 깊이 설명이 안되어있는 경우가 있는데 정말 중요한 차이가 하나 있다. 아래의 소스를 보자.

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

Handler와 사용하는 인터페이스가 비슷하지만 가장 중요한 차이는 바로 attach가 됐는지 여부를 확인한다는 것이다. View가 attach, 즉 화면에 보여지는 시기에 맞춰서 실행하려면 View.post()를 사용해야한다.

다시 말하면 View.post()는 attach되지 않으면 post된 모든 runnable들의 실행이 attach 될 때 까지 연기(postpone)된다. 여기서 getRunQueue는 View에만 존재하는 pending된 Runnable들의 HandlerActionQueue이다.

이는 View와 관련한 미묘한 타이밍 이슈를 처리할 때, 유용하게 사용된다. View가 GONE되거나 detach된 경우 아무리 post를 호출해도 등록된 콜백들은 동작하지 않게된다. 이런 섬세한 처리부분은 Handler만 가지곤 불가능하다.