Android basics: running code in the UI thread
Asked Answered
K

8

501

In the viewpoint of running code in the UI thread, is there any difference between:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

or

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

and

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Kalle answered 11/10, 2012 at 23:28 Comment(1)
To clarify my question: I supposed those code were called from a service thread, typically a listener. I also supposed there is a heavy work to accomplish either in the doInBackground() function of the AsynkTask or in a new Task(...) called before the first two snippet. Anyway the onPostExecute() of the AsyncTask is being put at the end of the event queue, right?Kalle
E
316

None of those are precisely the same, though they will all have the same net effect.

The difference between the first and the second is that if you happen to be on the main application thread when executing the code, the first one (runOnUiThread()) will execute the Runnable immediately. The second one (post()) always puts the Runnable at the end of the event queue, even if you are already on the main application thread.

The third one, assuming you create and execute an instance of BackgroundTask, will waste a lot of time grabbing a thread out of the thread pool, to execute a default no-op doInBackground(), before eventually doing what amounts to a post(). This is by far the least efficient of the three. Use AsyncTask if you actually have work to do in a background thread, not just for the use of onPostExecute().

Excipient answered 11/10, 2012 at 23:33 Comment(5)
Also note that AsyncTask.execute() requires you to call from the UI thread anyway, which renders this option useless for the use case of simply running code on the UI thread from a background thread unless you move all of your background work into doInBackground() and use AsyncTask properly.Excitant
@Excitant how I can check that I am calling AsyncTask from UI thread?Interlocution
@NeilGaliaskarov This looks like a solid option: https://mcmap.net/q/75293/-how-to-detect-ui-thread-on-androidMiddle
@NeilGaliaskarov boolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());Lunn
@NeilGaliaskarov for versions greater than or equals to M use Looper.getMainLooper().isCurrentThreadThanks
B
285

I like the one from HPP comment, it can be used anywhere without any parameter:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
Becker answered 11/8, 2014 at 19:1 Comment(1)
How efficient is this? Is it the same as other options?Lauraine
P
65

There is a fourth way using Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
Pastiness answered 11/10, 2012 at 23:36 Comment(4)
You should be careful with this. Because if you create a handler in a non UI thread you will post messages to the non UI Thread. A handler by default post message to the thread where it is created.Fu
to execute on the main UI thread do new Handler(Looper.getMainLooper()).post(r), which is the preferred manner as Looper.getMainLooper() makes a static call to main, whereas postOnUiThread() must have an instance of MainActivity in scope.Brooklyn
@Brooklyn I didn't know this method, will be a great way when you don't have nor Activity neither a View. Works great! thanks you veryy veryy much!Gui
@Fu Same is the case with onPreExecute call back method of AsyncTask.Then
M
18

The answer by Pomber is acceptable, however I'm not a big fan of creating new objects repeatedly. The best solutions are always the ones that try to mitigate memory hog. Yes, there is auto garbage collection but memory conservation in a mobile device falls within the confines of best practice. The code below updates a TextView in a service.

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

It can be used from anywhere like this:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Meerschaum answered 5/1, 2015 at 9:24 Comment(5)
+1 for mentioning GC and object creation. HOWEVER, I don't necessarily agree with The best solutions are always the ones that try to mitigate memory hog. There are many other criteria for best, and this has a slight smell of premature optimization. That is, unless you know you are calling it enough that number of objects created is an issue (compared to the ten thousand other ways in which your app is probably creating garbage.), what may be best is to write the simplest (easiest to understand) code, and move on to some other task.Anemic
BTW, textViewUpdaterHandler would be better named something like uiHandler or mainHandler, since it is generally useful for any post to main UI thread; it is not at all tied to your TextViewUpdater class. I would move it away from the rest of that code, and make it clear that it can be used elsewhere... The rest of the code is dubious, because to avoid creating one object dynamically, you break what could be a single call into two steps setText and post, that rely on a long-lived object that you use as a temporary. Unnecessary complexity, and not thread-safe. Not easy to maintain.Anemic
If you really have a situation where this is called so many times that it is worth caching uiHandler and textViewUpdater, then improve your class by changing to public void setText(String txt, Handler uiHandler) and adding method line uiHandler.post(this); Then caller can do in one step: textViewUpdater.setText("Hello", uiHandler);. Then in future, if needs to be thread safe, method can wrap its statements inside a lock on uiHandler, and caller remains unchanged.Anemic
I'm pretty sure that you can only run a Runnable once. Which absolutely break you idea, which is nice in overall.Tiepolo
@Tiepolo Nope, Runnable can be run multiple times. A Thread can't.Supposing
M
13

As of Android P you can use getMainExecutor():

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

From the Android developer docs:

Return an Executor that will run enqueued tasks on the main thread associated with this context. This is the thread used to dispatch calls to application components (activities, services, etc).

From the CommonsBlog:

You can call getMainExecutor() on Context to get an Executor that will execute its jobs on the main application thread. There are other ways of accomplishing this, using Looper and a custom Executor implementation, but this is simpler.

Middle answered 9/3, 2018 at 3:54 Comment(0)
M
13

If you need to use in Fragment you should use

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

instead of

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

Because There will be null pointer exception in some situation like pager fragment

Masonry answered 28/4, 2018 at 14:32 Comment(0)
A
8

use Handler

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
Assimilate answered 1/6, 2018 at 4:50 Comment(2)
Is there any reason why you chose to use the general purpose Handler over runOnUiThread?Nickelic
This will give a java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() if it's not called from the UI thread.Stoma
A
1

Kotlin version:

Handler(Looper.getMainLooper()).post {
   Toast.makeText(context, "Running on UI(Main) thread.", Toast.LENGTH_LONG).show()
}

Or if you are using Kotlin coroutines: inside coroutine scope add this:

withContext(Dispatchers.Main) {
   Toast.makeText(context, "Running on UI(Main) thread.", Toast.LENGTH_LONG).show()
}
Anglosaxon answered 8/12, 2021 at 13:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.