3

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?
        }
    }
}
S.Step
  • 441
  • 3
  • 8

1 Answers1

1

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.

Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • Yes, when I change layout_wirdth of button to wrap_content then I am getting the mentioned exception. Is not it unnatural? Because we are still dealing with non ui thread changing the UI thread created component(button) in this case. – S.Step Nov 02 '18 at 17:42
  • 1
    Yeah, I'm surprised to learn about this. But I suppose on some level it makes sense; the UI thread is the manager of the view hierarchy, but the view hierarchy isn't actually changing when the text changes (as long as the button size doesn't change). Personally, I'm still going to call UI-related methods from the UI thread. – Ben P. Nov 02 '18 at 17:43
  • @ Ben P. Thanks for your response! I was looking for to get CalledFromWrongThreadException for learning purpose just to find out why I would need to use handler, AsyncTask or your mentioned UI-related methods ... But in this case, in my opinion CalledFromWrongThreadException is not really reasonable exception, I mean it doesn't really justifies its name("...wrongThread"). I think there should have been implemented something new exception which would be thrown during this particular case... – S.Step Nov 02 '18 at 18:03