Mobile Development 16 min read

Why Updating UI on the UI Thread Can Still Throw CalledFromWrongThreadException in Android Dialogs

This article explains why performing UI updates—even on the main UI thread—can still trigger CalledFromWrongThreadException when a Dialog is created on a background thread, by analysing ViewRootImpl, Handler, Looper, and the view hierarchy with practical code examples.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Why Updating UI on the UI Thread Can Still Throw CalledFromWrongThreadException in Android Dialogs

In Android development a common mistake is to assume that UI updates are safe only when executed on the main thread. The article demonstrates that this assumption is wrong when a Dialog (or any view hierarchy) is created on a background thread, because the ViewRootImpl that manages the view is bound to the thread that instantiated it.

The author first shows the typical crash log:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Then a minimal MainActivity is presented, where a button click starts a new Thread that simulates a network request and directly calls showQuestionInDialog to display a custom QuestionDialog :

package com.example.testviewrootimpl;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private Button mBtnQuestion;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnQuestion = findViewById(R.id.btn_question);
        mBtnQuestion.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                requestAQuestion();
            }
        });
    }

    private void requestAQuestion() {
        new Thread(){
            @Override
            public void run(){
                try { Thread.sleep(1000); } catch (InterruptedException e){e.printStackTrace();}
                String title = "鸿洋帅气吗?";
                showQuestionInDialog(title);
            }
        }.start();
    }

    private void showQuestionInDialog(String title){
        QuestionDialog dialog = new QuestionDialog(this);
        dialog.show(title);
    }
}

The QuestionDialog layout consists of a TextView for the title and two Button s:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24dp"
        android:textStyle="bold" />
    <Button
        android:id="@+id/btn_yes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_title"
        android:text="是的" />
    <Button
        android:id="@+id/btn_no"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@id/btn_yes"
        android:layout_toRightOf="@id/btn_yes"
        android:text="不是" />
</RelativeLayout>

Running the app crashes with the above exception because the Dialog (and thus its ViewRootImpl ) is created inside the background thread. The author first tries a quick fix by calling Looper.prepare() and Looper.loop() inside showQuestionInDialog , which temporarily prevents the crash but does not solve the underlying problem.

Next, the article adds a Handler bound to the main looper and posts UI updates to it:

private Handler sUiHandler = new Handler(Looper.getMainLooper());

mBtnNo.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        sUiHandler.post(new Runnable() {
            @Override
            public void run() {
                String s = mTvTitle.getText().toString();
                mTvTitle.setText(s + "?");
            }
        });
    }
});

Surprisingly, after several clicks the app crashes again with the same CalledFromWrongThreadException . The reason is that the ViewRootImpl that owns the dialog was created on the background thread, so its internal mThread field points to that thread. When ViewRootImpl.checkThread() is invoked (via requestLayout() from TextView.setText() ), it compares the current thread with mThread**, not with the UI thread.

The article then shows the relevant source snippets of ViewRootImpl :

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mThread = Thread.currentThread();
}

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

Thus, the rule is: UI updates must be performed on the same thread that created the ViewRootImpl , which is not necessarily the main UI thread. The article also prints the view‑parent hierarchy of a TextView inside the dialog, revealing the chain ending with ViewRootImpl :

D/lmj: AppCompatTextView
D/lmj:  RelativeLayout
D/lmj:   FrameLayout
D/lmj:    FrameLayout
D/lmj:     DecorView
D/lmj:      ViewRootImpl

Finally, the author concludes that the misconception "only UI thread can update UI" is inaccurate; the correct constraint is "only the thread that created the view hierarchy can modify it". The article ends with thought‑provoking questions and encourages readers to explore other scenarios where this rule applies.

AndroidLooperHandlerUI ThreadViewRootImplCalledFromWrongThreadExceptionDialog
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.