I am confused about how the Android system works, specially when it updates the view hierarchy. We all know that we should not update any view from any thread other than UI (Main) thread. And even Android system throws exception when we try to do so. The other day I was trying to implement a custom progress showing view in my application. So I started with using standard Java threads and handler combo.
What I found surprised me as I was able to update a TextView from background thread.
new Thread(new Runnable() {
@Override
public void run() {
mTextView.setText("I am " + Thread.currentThread().getName());
}
}).start();
After which I tried updating other views also, which worked pretty well. So I tried putting a sleep call in background Thread.
new Thread(new Runnable() {
@Override
public void run() {
mTextView.setText("Thread : before sleep");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mTextView.setText("Thread : after sleep");
}
}).start();
And it crashes as expected saying
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Then I tried putting the setText() calls in loop for 100, 1000 times before and after the sleep() call. Of course the app crashes every single time but I was able to see the "Before Sleep" text on my textview.
So my question is when does system detect that some non-UI thread is trying to update the view. And why does not it work when there is no sleep() call in non-UI thread ?
Thread is a parallel process to UI thread. when you try to put the sleep function inside a thread, the execution of the thread stops. The answer to your question is inside the question itself. it says - Only the original thread that created a view hierarchy can touch its views. so other are two thread running one ui thread and the other one which you created. when you call the sleep method. your thread stops execution for which there is no synchronization with the ui thread. and when your thread tries to change the text of textview, both the thread are not in sync. before the sleep the thread were in sync. and after sleep they are not in sync,.
I run your code snippet with
sleep
in Lollipop and it crashes. The stack trace is:So the key hides around line 4057 of
TextView.setText
which is:We can see if the
mLayout
of theTextView
isnull
,checkForRelayout()
won't be called and thus the app will not crash. And themLayout
will be initialized inonDraw
ofTextView
. So the app doesn't crash the first timesetText
is called becausemLayout
is null. After drawing,mLayout
is initialized and cause the app to crash the second timesetText
is called.I guess you start the
Thread
before theTextView
is drawn (e.g. inonCreate
oronResume
). Right?Whether the app crashes or not depends on the TIME you call
setText
. If you callsetText
before theTextView
is first drawn, everything is ok. Otherwise the app crashes.