简体   繁体   中英

If any worker thread can update UI in android?

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);
        }
    }

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.

The reason here is the thread was able to make changes in the TextView object only till it was not visible on UI screen ie 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 ie 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 .

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM