8

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 ?

apersiankite
  • 531
  • 2
  • 11
  • Just use android.os.Handler - it will always run its runnable on the UI-thread. – Dogcat Dec 07 '15 at 11:47
  • when? see `android/view/ViewRootImpl.java` the method name is `checkThread` – pskink Dec 07 '15 at 11:49
  • @Dogcat : I know about Handler. But my question is that why does not system crashes in first case ? – apersiankite Dec 07 '15 at 11:50
  • @pskink : Of course, I have checked that method, by "when" I mean to ask about the use-cases when system calls this check. – apersiankite Dec 07 '15 at 11:51
  • it checks when your UI tree is added to the root window, but you dont know excactly when it happens, so the rule of the thumb is: **don't touch your UI from non UI thread. PERIOD** – pskink Dec 07 '15 at 11:55
  • Ha, that's funny, your code doesn't crash on my android device (4.4). – Dogcat Dec 07 '15 at 12:06
  • @Dogcat : It's crashing on my nexus 5 (Android 4.4). Try the snippet with "sleep()" call. The other one works just fine. Thats where the confusion lies. – apersiankite Dec 07 '15 at 12:34
  • @pskink : I believe the UI tree is added to root window whenever we do 'setContentView'. – apersiankite Dec 07 '15 at 12:35
  • I just did, I tried that http://pastebin.com/wavcWcmt – Dogcat Dec 07 '15 at 12:37
  • @Dogcat Not working on my Android 4.4 and Android 5.0.1. Can you try running it on emulator. – apersiankite Dec 07 '15 at 12:39
  • 1
    My problem is that why does system behaving like this, throwing exception only sometimes, why not every time . How does Thread.sleep() changes the system behaviour ? – apersiankite Dec 07 '15 at 12:45
  • @apersiankite I wouldn't trust the emulator really. Anyhow, I've got two Android devices with me (4.4 and 4.2). The code I posted runs perfectly on 4.2 as well. God, do I love Android. – Dogcat Dec 07 '15 at 12:51
  • @Dogcat : Haha, but it is not working here . I have tested the same code on 4 different phone devices. – apersiankite Dec 07 '15 at 12:54
  • 1
    @pskink : Actually I never touch UI elements from non-UI thread, in-fact I rarely use Threads. One of my colleague (college fresher) did something like this, so the doubt came into my mind. BTW my doubt arises right on the point of "you are no allowed to do that", because I am able to do it. System is not behaving consistently. – apersiankite Dec 07 '15 at 13:31
  • yes `System is not behaving consistently` so what? this is like "don't cross the street on the red light, you are not allowed to do that" and you are answering "no, i am able to cross the street on the red light" but sooner or later you will be hit by some car – pskink Dec 07 '15 at 13:47
  • System is not behaving consistently, so `I just want to know the reason`. Thats it,nothing much. I am not going to update the views from other non-UI threads. And BTW , I think that analogy does not fit well. – apersiankite Dec 07 '15 at 14:21
  • so do you want to know the details of `ViewRootImpl`? isn't it just enough if they say: `"don't touch UI from non UI thread, or you will get unknown results"` ? maybe the full checks would slow down the system, maybe it would make it much bigger in size? maybe they just missed some points to check it? does it really matter? no, if you are not going to update the views from other non-UI threads – pskink Dec 07 '15 at 14:40
  • Well it matters to me. See it all depends on person, whether you want to know about internal working of Android system or not. It's fine that you don't want to answer or don't know the answer. I had a doubt so I posted it here, assuming some might know the reason. – apersiankite Dec 07 '15 at 15:09

2 Answers2

4

I run your code snippet with sleep in Lollipop and it crashes. The stack trace is:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
        at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:909)
        at android.view.ViewGroup.invalidateChild(ViewGroup.java:4690)
        at android.view.View.invalidateInternal(View.java:11801)
        at android.view.View.invalidate(View.java:11765)
        at android.view.View.invalidate(View.java:11749)
        at android.widget.TextView.checkForRelayout(TextView.java:6850)
        at android.widget.TextView.setText(TextView.java:4057)
        at android.widget.TextView.setText(TextView.java:3915)
        at android.widget.TextView.setText(TextView.java:3890)
        at com.test.MainActivity$16.run(MainActivity.java:1126)
        at java.lang.Thread.run(Thread.java:818)

So the key hides around line 4057 of TextView.setText which is:

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

We can see if the mLayout of the TextView is null, checkForRelayout() won't be called and thus the app will not crash. And the mLayout will be initialized in onDraw of TextView. So the app doesn't crash the first time setText is called because mLayout is null. After drawing, mLayout is initialized and cause the app to crash the second time setText is called.

I guess you start the Thread before the TextView is drawn (e.g. in onCreate or onResume). Right?

Whether the app crashes or not depends on the TIME you call setText. If you call setText before the TextView is first drawn, everything is ok. Otherwise the app crashes.

shhp
  • 3,653
  • 2
  • 18
  • 23
  • So you mean to say that Thread.sleep() somehow initializes the mLayout in TextView . Because even if you call the first setText() in a loop of 1000 times, it behaves the same. – apersiankite Dec 07 '15 at 12:56
  • You can try to add `sleep` before the first invoke of `setText`. The app will crash too. – shhp Dec 07 '15 at 12:58
  • During `Thread.sleep()` the `TextView` is already being drawn which also means the `mLayout` is initialized. – shhp Dec 07 '15 at 12:59
  • He might be onto something. I just tried sleeping for two seconds instead of 100ms and now my app crashes as well. – Dogcat Dec 07 '15 at 13:02
  • @Dogcat I tried sleeping the thread for 100 or 200 ms like in your code, but still my app crashes . – apersiankite Dec 07 '15 at 13:08
  • @apersiankite I can add some debug to the Thread and TextView classes and make a custom Android build tomorrow. I mean, If you're that interested in getting the answer. – Dogcat Dec 07 '15 at 13:14
  • @Dogcat : Wow, would love to see that. Thanks in advance. – apersiankite Dec 07 '15 at 13:34
-1

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,.

  • 3
    First of all **Thread is not a process** . Forget about sleep() call for a minute and try the code without sleep. The code works fine. That's where my doubt lies, why would android system allow any non-UI thread to update the view. **It does not throw exception **.And by the way there is no synchronization between UI thread and any other thread, whether you put it into sleep or not. – apersiankite Dec 07 '15 at 13:00
  • 3
    Are you kidding me ?? I have already told you (at your last answer )that I know all the multithreading concerns and related patterns to use while writing code for Android. **Kindly re-read my question.** – apersiankite Dec 07 '15 at 13:05