1

I suppose that we can't update any UI view elements (TextView, EditText etc) in any worker thread but in the below example, I am able to update views from a worker thread, not all the times but only sometimes.

See below example where I'm updating UI elements in worker thread -

public class AndroidBasicThreadActivity extends AppCompatActivity
{
    public static TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_android_basic_thread);

        textView = (TextView) findViewById(R.id.textview);

        MyAndriodThread myTask = new MyAndriodThread();
        Thread t1 = new Thread(myTask, "Bajrang Hudda");
        t1.start();
    }
}

Here is my worker thread -

class MyAndriodThread implements Runnable
{
    @Override
    public void run()
    {
        AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
    }
}

Believe me, I won't get any exception saying -

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

And I can see the update UI on emulator.

But if I do any heavy task (sleeping) in worker thread then I got the expected above exception, why it is happening like this? I am a new guy on android multithreading please get me out from this confusion.

If i will change my worker thread to this i will get above exception -

class MyAndriodThread implements Runnable
{
    @Override
    public void run()
    {
        try
        {
            Thread.sleep(2000);
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
        System.out.println("Child thread completed.");
    }
}

And if my UI or main thread is waiting (join()) then i get exact output no exception at all, see this -

MyAndriodThread myTask = new MyAndriodThread();
        Thread t1 = new Thread(myTask, "Anhad");
        t1.start();
        try
        {
            t1.join();
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }

Update
First I thought it's rendering scheme then i changed my program by using ProgressBar..then i got really unexpected output... Still no exception mean i can update my progress bar in worker thread. See below-

 @Override
    public void run()
    {
        for(int i = 1; i<=10; i++)
        {
            try
            {
                Thread.sleep(2000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            activity.progressBar.setProgress(i);
        }
    }
Siddharth jain
  • 447
  • 1
  • 5
  • 13
Bajrang Hudda
  • 3,028
  • 1
  • 36
  • 63
  • [http://stackoverflow.com/questions/5185015/updating-android-ui-using-threads](http://stackoverflow.com/questions/5185015/updating-android-ui-using-threads) – Sinan Talebi Feb 16 '17 at 06:44
  • In this link also saying that we can't touch anything in UI thread, but believe me we can see the exact output on emulator. – Bajrang Hudda Feb 16 '17 at 06:47
  • 1
    Better to go with this article. https://developer.android.com/topic/performance/threads.html – Priyavrat Feb 16 '17 at 06:50

4 Answers4

8

So Bajrang Hudda I am guessing you are mostly satisfied by the ShadyGoneInsane answer. I truly agree with him, that once a view is rendered and attached to the Window you cannot access it from worker thread directly.

To answer your updated question regarding progressBar, ProgressBar internally uses a Runnable to update its progress which it then post(r) using its handler.

To explain in detail, when you try to update a ProgressBar from worker thread progressBar.setProgress(i) the progressBar first checks if the call is made on MainThread, if so update directly; else it creates a new Runnable RefreshProgressRunnable to update the progress, which it then post(r) using its handler.

Eg new Handler().post(r)

and I am sure you have also used something like this before view.post(r)

ProgressBar source code check for method setProgress()

Hope this clears your doubt.

Siddharth jain
  • 447
  • 1
  • 5
  • 13
  • you mean progressBar.setProgress(i) internally checks is it from UI or worker thread? and it means i can update my progressbar any time form worker thread without any issue at all but I can't update other view except ProgressBar. – Bajrang Hudda Feb 17 '17 at 07:02
  • I am not yet sure except progressBar because like you I also found this and was curios to know why this is working from worker thread and hence found this explanation. May be there are some other widget that uses the same way. – Siddharth jain Feb 17 '17 at 07:06
  • 1
    progressBar.setProgress(i) you can use this, but it is not advised and the reason for that is, as two thread are referencing the same view the result may be uncertain. – Siddharth jain Feb 17 '17 at 07:07
  • 1
    I have gone through the complete source of Progressbar. @BajrangHudda as stated by siddarth, it is actually going in this fashion. – Priyavrat Feb 17 '17 at 07:08
  • 1
    private synchronized void refreshProgress(int id, int progress, boolean fromUser, boolean animate) {if (mUiThreadId == Thread.currentThread().getId()) { doRefreshProgress(id, progress, fromUser, true, animate); } else {if (mRefreshProgressRunnable == null) { mRefreshProgressRunnable = new RefreshProgressRunnable(); }final RefreshData rd = RefreshData.obtain(id,progress,fromUser,animate); mRefreshData.add(rd);if (mAttached && !mRefreshIsPosted) { post(mRefreshProgressRunnable); mRefreshIsPosted = true;}}} – Priyavrat Feb 17 '17 at 07:11
  • 1
    @BajrangHudda I think this answers your question !! – shadygoneinsane Feb 20 '17 at 09:55
6

The reason here is the thread was able to make changes in the TextView object only till it was not visible on UI screen i.e not rendered. It is only after the view rendering, that any thread except the Main thread may not make changes to any UI components.

So when you do this :

class MyAndriodThread implements Runnable
{
    @Override
    public void run()
    {
        try
        {
            Thread.sleep(2000);
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
        System.out.println("Child thread completed.");
    }
}

you are trying to change text after view rendering which causes the Exception

while in the below code snippet the thread was able to make changes in the TextView object because it was not visible on UI screen i.e not rendered.

class MyAndriodThread implements Runnable
{
    @Override
    public void run()
    {
        AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
    }
}

As pointed by @JohnnyAW - when you use join() you delay rendering until the worker is ready, so you don't get exception even if the worker thread sleeps

when you did this Thread.sleep(2000); the TextView was rendered in between and then you tried to touch your view from this workerThread which caused the crash

you can also try Thread.sleep(0); and see it won't raise any exception and your app will not crash .

shadygoneinsane
  • 2,226
  • 1
  • 24
  • 47
  • [Bajrang Hudda](http://stackoverflow.com/users/3448003/bajrang-hudda) Try this code in `onResume()` method, it will not work. – Pankaj Feb 16 '17 at 07:03
  • @shadygoneinsane what if my UI thread is waiting to complete for worker thread?? see my update question please. – Bajrang Hudda Feb 16 '17 at 07:12
  • 1
    @BajrangHudda when you use `join()` you delay rendering until the worker is ready, so you don't get exception even if the worker thread `sleep`s – JohnnyAW Feb 16 '17 at 07:30
  • @BajrangHudda Does this answer your question ? – shadygoneinsane Feb 16 '17 at 10:11
  • Which thread is responsible to render TextView here.. Main thread or MyAndroidThread (worker thread)?? I think only my worker thread is responsible to render not main thread. Because rendering line will be executed by worker thread only. – Bajrang Hudda Feb 16 '17 at 10:24
  • @BajrangHudda you should [refer this](https://developer.android.com/topic/performance/threads.html) It clearly states : **By design, Android View objects are not thread-safe. An app is expected to create, use, and destroy UI objects, all on the main thread. If you try to modify or even reference a UI object in a thread other than the main thread, the result can be exceptions, silent failures, crashes, and other undefined misbehavior.** so your assumption is wrong about which thread renders any `View` object ..Hope this helps – shadygoneinsane Feb 16 '17 at 10:41
  • @shadygoneinsane, its okay but now see my updated question again..and see i'm updating my progress bar 10 times without any issue at all. – Bajrang Hudda Feb 17 '17 at 06:30
1

This behaviour really happens. As it is clearly mentioned at android's developer portal.

There is a para with Explicit references which states that

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.

So this can happens and undefined.

https://developer.android.com/topic/performance/threads.html

Priyavrat
  • 451
  • 3
  • 14
-1

You cannot update anything in the ui thread from a background thread, to do that you need to use a Handler or use AsyncTask.onProgressUpdate, for more details have a detail look at AsyncTask.

Update

It is not recommended to update your UI from worker thread without involving Handler or use Activity.runOnUiThread(Runnable). In your case when you are updating your TextView from MyAndriodThread, your view is in transition state, if you will try to update your UI your view may get the update or results in crashing, according to documentation:

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

Haris Qurashi
  • 2,104
  • 1
  • 13
  • 28