Why I am not getting expected CalledFromWrongThreadException when trying to change UI view component from non-ui thread

Viewed 67

As a beginner in android programming, I am taught that can't change UI view components from non-ui thread, but in the following simple code snippet it seems that it can be done(non ui thread changes button text which is created by UI thread)... I've done research and I should get CalledFromWrongThreadException. Please help?

public class MainActivity extends AppCompatActivity {

    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
    }

    public void clickDo(View view){
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }

    class MyRunnable implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            button.setText("newText"); //<-------- here?
        }
    }
}
1 Answers

When you call TextView.setText(), a number of things happen. The actual act of modifying the text that's being displayed is a separate action from checking to see if the view's layout needs to be changed, and the changed layout is the only thing that needs to be done on the UI thread.

In the source code for setText(), there's this little bit:

if (mLayout != null) {
    checkForRelayout();
}

Unlike the previously-linked duplicate, we know that mLayout is non-null in this case. So we know that checkForRelayout() is being called.

However, looking at checkForRelayout() shows that there are certain circumstances where the method returns without ever calling requestLayout() and/or invalidate(). In these cases (where requestLayout() isn't called), the expected CalledFromWrongThreadException will not be thrown.

Essentially, if the TextView has a fixed width, and the new text has the same number of lines as the old text, requestLayout() won't be called.

That means that you are free to call setText() from a non-UI thread as long as you know that the TextView has a fixed width and the new and old text have the same number of lines.


I did some experimentation to verify this. Indeed, when my Button used a wrap_content width, I always got the crash with your code. However, when I changed it to match_parent, I only got the crash when my new text would wrap to a second line.

Related