Setting text in TextView from background theme anomaly

I just started playing with Android Concurrency / Loopers / Handers and I just ran into a strange anomaly. The code below does not block me from setting text to the TextView from another thread.

TextView tv;
Handler backgroundHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    tv = (TextView) findViewById(R.id.sample_text);
    Runnable run = new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            backgroundHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    String text = (String) msg.obj;
                    tv.setText(Thread.currentThread().getName() + " " + text);
                }
            };
            Looper.loop();
        }
    };

    Thread thread = new Thread(run);
    thread.setName("Background thread");
    thread.start();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Message message = backgroundHandler.obtainMessage();
    message.obj = "message from UI";
    backgroundHandler.sendMessage(message);
}

      

And guess what will happen

enter image description here

But, when I sleep for a while the background thread

backgroundHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String text = (String) msg.obj;
                tv.setText(Thread.currentThread().getName() + " " + text);
            }
        };

      

it throws an exception as i expected

07-03 18:54:40.506 5996-6025/com.stasbar.tests E/AndroidRuntime: FATAL EXCEPTION: Background thread
                                                             Process: com.stasbar.tests, PID: 5996
                                                             android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
                                                                 at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7275)

      

Can someone explain to me what happened? Why was I able to set text from another thread?

+3


source to share


2 answers


On the first try, your code is wrong because you created Handler

for different threads. This causes the looper / handler to run on different threads. Based on your comment, I think you are aware of this problem and you want to understand why the exception is not thrown on the first try and the second.

You should beware of this: accessing a UI element on a different thread other than UI Thread

invokes undefined behavior . It means:

  • Someday you will see that it works.
  • someday you won't. You will encounter an exception, as you have seen.

This means that accessing a UI element across different threads is not always 100% playable.

Why should you only access all UI elements on the UI thread? Because handling a UI element (changing internal state, drawing to screen ...) is complex and requires synchronization between related parties. For example, you are calling TextView#setText(String)

on 2 fragments that are displayed on the screen. Android does not do this at the same time, but all jobs are forwarded to the UI message queue and done sequentially. This is true not only in terms of your application, but also in terms of the entire Android system. Updating from the status bar called by the system, updating from your application called by your application, always push activities to the queue of the same UI Message before processing.

When you access and modify UI elements on different threads, you've broken this process. This means that it is possible that two threads can access and change an item in the same state and at the same time. As a result, you will encounter a race condition at some time. This happens when an error occurs.



The explanation for your situation is difficult because there is not enough data to analyze. But there are several reasons:

  • On the first try, the TextView is not displayed on the screen yet. This way another thread was able to make changes to the TextView. But on the second try, you sleep for 1 second. At this time, all views were rendered and displayed successfully on screen, so an exception was thrown. You can try it Thread.sleep(0)

    and hopefully your code won't work.
  • This will happen in some situations, but it's hard to guess why. This is at least both your thread and the ui thread are accessing the same lock object, exception.

You can read more about thread issues here Android Thread

Explicit links

Many non-mainstream tasks have the ultimate goal of updating user interface objects. However, if one of these threads accesses an object in the view hierarchy, application instability can occur: if a worker thread changes the properties of that object at the same time that any other thread is referring to the object, the results are undefined.

Hope it helps you.

+3


source


Only the user interface can make changes to the user interface elements. In other words, you cannot edit UIs from background threads.

So, instead tv.setText(Thread.currentThread().getName() + " " + text);

use the following code inside backgroundHandler

: -



 runOnUiThread(new Runnable() {
    public void run() {
        tv.setText(Thread.currentThread().getName() + " " + text);
    }
});

      

0


source







All Articles