I've just started playing with Android Concurrency/Loopers/Handers, and I have just faced with strange anomaly. The code below doesn't block me from setting text on TextView from different 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 happen
But, when I sleep background thread for a while
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 does throw 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 me what happened ? Why I was able to set text from different thread ?
From the very first try, your code is wrong because you created Handler
on different thread. This leads to looper/handler is run on different thread. Based on your comment, I guess you know this issue and you want to understand why exception doesn't throw on first try but second.
You should beware this: Accessing UI Element on different thread except from UI Thread
causes undefined behavior. This means:
That means Accessing UI Element on different thread isn't always 100% reproducible.
Why you should access all UI Elements only on UI Thread? Because processing UI Element (change internal state, draw to screen ...) is a complex process and need to synchronized between related parties. For example, you call TextView#setText(String)
on 2 fragments that both visible on screen. Android doesn't do this concurrently but pushing all jobs into an UI message queue and do that sequentially. This is also true not only from your application viewpoint but also from whole Android system perspective. Updating from status bar that called by system, updating from your app that called by your application always push actions to same UI Message queue before processing.
When you access and modify UI elements on different thread, you broke that process. That means maybe two threads might access and modify an element at same state and same time. As the result, you will meet race condition at some time. That when error occurs.
Explaining for your situation is hard because not enough data for analyzing. But there are some few reasons:
Thread.sleep(0)
and hopefully your code doesn't crash.You can read more about thread issue here Android Thread
Explicit references
Many tasks on non-main threads have the end goal of updating UI objects. However, if one of these threads accesses an object in the view hierarchy, application instability can result: If a worker thread changes the properties of that object at the same time that any other thread is referencing the object, the results are undefined.
Hope this help you.
Only the UI Thread can make edits to UI items. In other words, you can not make user interface edits from background threads.
So, instead of tv.setText(Thread.currentThread().getName() + " " + text);
, use the following code inside your backgroundHandler
:-
runOnUiThread(new Runnable() {
public void run() {
tv.setText(Thread.currentThread().getName() + " " + text);
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With