[Android] Toast는 어떻게 동작할까?

우발자·2026년 2월 14일

🏁 시작하기 앞서..

skydoves님의 글을 읽으며, 마치 나의 치부를 정확히 짚어낸 것 같은 기분이 들었다.
그동안 나는 면접을 준비하거나 공부를 할 때, 어떤 기술을 사용해 왔는지, 그리고 그 기술을 어떻게 활용했는지에만 집중해왔다.

솔직히 말하면, 그 기술이 내부적으로 어떻게 동작하는지에 대해서 깊이 고민하며 공부한 적은 많지 않았다.
“나는 이 기술을 사용할 수 있다”는 사실을 보여주는 데에만 신경 썼을 뿐, 그 이면에 있는 구조나 원리에 대해서는 크게 신경 쓰지 않았던 것이다.

지금 돌아보면, 그것이 나의 분명한 약점이었다.
분명 실무에서는 익숙하게 사용하던 기술들이었지만, 막상 면접에서 “이 기술은 내부적으로 어떻게 동작하나요?”라는 질문을 받으면 선뜻 대답하지 못했던 기억이 적지 않다.
사용은 하고 있었지만, 이해하고 있다고 말하기에는 부족한 상태였던 것이다.

최근에는 내부 동작을 항상 살펴보려고 한다.
그래서 오늘은 안드로이드에서 흔히 사용하지만 어떻게 동작하는지는 잘 몰랐던 Toast에 대해 알아보려한다.


1️⃣ Toast는 Context가 왜 필요할까

☝️ System Service에 접근하기 위함

Toast는 WindowManager를 통해 화면에 표시되낟. 이때 Context에서 지원하는 getSystemService() 함수를 통해 WindowManager 객체를 가져와서 사용한다.

private WindowManager getWindowManager(View view) {
    Context context = mContext.get();
    if (context == null && view != null) {
        context = view.getContext();
    }
    if (context != null) {
        return context.getSystemService(WindowManager.class);
    }
    return null;
}

// e.g.
WindowManager.addView(toastView, layoutParams)

Window는 최상위 UI 컨테이너로 우리가 보는 모든 UI ( Activity, Dialog, Toast)등 모두 Window에 붙어 보여지게 된다.

✌️ Resource에 접근하기 위함

Toast의 기본 위치, Gravity값, 레이아웃 등은 모두 System Resource에서 가져온다. System Resource는 Android OS 내부 리소스를 의미하며ㅡ 시스템 전체에서 공유한다.

mTN.mY = context.getResources().getDimensionPixelSize(
    com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
    com.android.internal.R.integer.config_toastDefaultGravity);

🤟 Layout을 Inflation하기 위함

Toast의 기본 텍스트 뷰를 생성할 때 LayoutInflater가 필요하며, 이 역시 Context를 통해 접근한다.

public static View getTextToastView(Context context, CharSequence text) {
    View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT, null);
    TextView textView = view.findViewById(com.android.internal.R.id.message);
    textView.setText(text);
    return view;
}

2️⃣ I/O 스레드에서 Toast를 띄우면?

결론부터 말하면 I/O 스레드에서 Toast를 띄우면 RuntimeException이 나면서 크래시가 발생한다.

💁 왜그럴까?

/**
 * Constructs an empty Toast object.  If looper is null, Looper.myLooper() is used.
 * @hide
 */
public Toast(@NonNull Context context, @Nullable Looper looper) {
    mContext = context;
    mToken = new Binder();
    looper = getLooper(looper);
    mHandler = new Handler(looper);
    mCallbacks = new ArrayList<>();
    mTN = new TN(context, context.getPackageName(), mToken,
            mCallbacks, looper);
    mTN.mY = context.getResources().getDimensionPixelSize(
            com.android.internal.R.dimen.toast_y_offset);
    mTN.mGravity = context.getResources().getInteger(
            com.android.internal.R.integer.config_toastDefaultGravity);
}

private Looper getLooper(@Nullable Looper looper) {
    // Looper 검증 로직!
    if (looper != null) {
        return looper;
    }
    return checkNotNull(Looper.myLooper(),
            "Can't toast on a thread that has not called Looper.prepare()");
}

토스트는 내부적으로 Handler를 사용하여 처리하고 있다. Handler는 Looper가 있는 스레드에서 동작할 수 있는데, Main 스레드의 Looper는 앱이 시작되는 시점에 초기화가 이뤄지는 반면, I/O 스레드는 기본적으로 Looper가 초기화 되지 않는다.

public static @NonNull <T> T checkNotNull(
        final T reference,
        final @NonNull @CompileTimeConstant String messageTemplate,
        final Object... messageArgs) {
    if (reference == null) {
        throw new NullPointerException(String.format(messageTemplate, messageArgs));
    }
    return reference;
}

그래서 Looper.myLooper()가 null을 반환하여 checkNotNull()에서 RuntimeException을 던진다.

따라서 Toast를 띄울려면 I/O 스레드에서 Main 스레드로 전환해야된다.


3️⃣ 여러 앱에서 동시에 Toast를 띄우면?

결론부터 말하자면 여러앱에서 동시에 Toast를 띄우면 시스템이 전부 받아서 순서대로 보여준다. 즉, 겹쳐서 동시에 뜨지는 않는다.

왜 겹치지 않을까?

Toast는 앱마다 따로 관리되는 게 아니라,
시스템 레벨에서 중앙 관리되기 때문이다.

즉, 각 앱이 Toast를 호출하면 시스템에서 하나의 큐로 들어오게되고
순차적으로 표시가 된다.
이걸 관리하는 주체가 Android 시스템이다.

그래서 앱이 담당하는게 아니여서 앱이 종료가 되도 Toast는 유지가 될 수 있었던 것이다.


🌜 결론

내가 흔히 사용하던 Toast조차, 익숙함에 속아 더 깊이 알아보려 하지 않았다는 사실이 부끄럽게 느껴졌다.
Toast는 단순하고 쉬워 보이는 API였지만, 그 내부에는 Window, Looper, Handler 등 복잡한 시스템 레벨의 로직이 촘촘히 엮여 있었다.

이번 정리를 통해 다시 한 번 느낀 것은,
캡슐화는 복잡함을 없앤 것이 아니라, 잘 숨겨두었을 뿐이라는 사실이다.
그리고 그 안을 들여다보려는 노력이 결국 개발자의 깊이를 만든다는 것도.

참조
https://velog.io/@mraz3068/Android-Toast-Deep-Dive

profile
어제보다 나은 개발자가 되자

0개의 댓글