2

I am trying to understand how UI thread's event queue works. I'm trying to run a code that can be broken into many parts but unfortunately it must run on the UI thread. So, in order to not block the UI thread and receive a ANR I was wondering if I can break that code in many Runnable objects and run them using runOnUiThread from another thread.

My question is, will this block the UI thread? If, for example, I have a piece of code that definitely runs in over 5 seconds and I break this code into, let's say 1000 Runnable objects, and add them to the event queue of the UI thread, will other events get processed by the UI thread between them?

Edit: I think I found a better way to express myself in case the above explanation is confusing.

  • The 1000 Runnable objects was just an example, in actual code I want to have at most 10.
  • Basically, I want 10 Runnable objects, each one initialising an Ad network on the UI thread. I want these Runnable objects to run one after another, not in parallel. Also, I want the UI thread to be able to process other events between running these objects, so that I don't get an ANR in case running all 10 run methods will take more than 5 seconds.

NOTE: I don't know why initialising Ad networks must be done on the UI thread, but it must, otherwise the app crashes. It also states in some of the networks' sdks' documentation that initialisation must happen on the UI thread. This is why I need to run them one after another on UI thread and I can't run them in parallel in the background. Also, the app is actually a OpenGl game, so calls to running the Runnable objects will be made from a GL thread, not the main thread, so they will be added to the event queue, and not executed immediately.

Boby
  • 856
  • 7
  • 9
  • 1
    Could you add a little example? Is it _really_ necessary to run all of that code on the UI? – Fildor Aug 21 '17 at 07:31
  • 2
    Why must it be running on the UI thread? What drives this requirement? The UI thread exists to *serve* your UI - dispatch events and update UI components. It should **not** be doing anything else! – GhostCat Aug 21 '17 at 07:34
  • @pskink runnable would be executed by the UI thread. If we call runOnUiThread on the main thread itself, it will simply ignore that and execute the code in that very same cycle. Better to use a handler to post runnables. – Sarthak Mittal Aug 21 '17 at 07:43
  • see `MessageQueue#addIdleHandler` – pskink Aug 21 '17 at 07:54
  • Thanks everyone for the comments, so: what I am doing is initialising Ad networks. I don't know why they must be initialised on the UI thread, but they must, if I initialise them in any other thread app crashes(it also states in some of the networks' documentation that they need to be initialised on UI thread). @Sarthak I'm calling the code from another thread(it an OpenGL game so almost everything is done on a GL thread) so all Runnables will be posted to the queue, they won't get executed immediately. – Boby Aug 21 '17 at 08:31
  • @pskink Not yet, I'm reading documentation now. I'm c++ developer so I'm kindof slow with java. Can you please confirm that for using the IdleHandler the way I want I should write something like this: 'Looper.getMainLooper().myQueue().addIdleHandler(new IdleHandler(){...});' ? – Boby Aug 21 '17 at 09:20
  • yes this is correct, if you are new to this you could also `Handler#post()` your `Runnables` one by one (not 10 at once) but when the first `Runnable` finishes then `post` the second etc - that way you will not "starve" the main UI message queue – pskink Aug 21 '17 at 09:32
  • and you should create a new handler like this: `new Handler(Looper.getMainLooper())` – pskink Aug 21 '17 at 09:44
  • @pskink Thank you very much for your time. How can I know when a Runnable is finished without using IdleHandler? Just add call to Handler#post() at the end of my run() function? – Boby Aug 21 '17 at 10:30
  • Great, thanks. Unfortunately I can't reproduce the ANR issue on my devices so I'll have to publish new version and wait a few days to see if I get new reports on the Google Play dashboard. – Boby Aug 21 '17 at 10:36
  • yes, this is how you should chain your `Runnable`s - you can even forget about `IdleHandler` and simply `post` the first `Runnable` any time you want – pskink Aug 21 '17 at 10:36
  • @pskink Yeah, actually this seems very logic, thank you. I think you should post this chain method as an answer, it seems the most secure method to run consecutive Runnables on the UI thread without blocking it. – Boby Aug 21 '17 at 10:43
  • @pskink And just one more thing, should I create a new Handler in each Runnable, or create just one and reuse it? – Boby Aug 21 '17 at 10:48
  • reuse it, one is enough – pskink Aug 21 '17 at 11:02
  • @pskink Ok, can I do the same with the Runnable object? Basically call inside the Runnable's run method something like myHandler.post(this); ? – Boby Aug 21 '17 at 11:03
  • yes you can use the same `Runnable` but you would need to store some "state" inside to control which step to run (1-10) so your `run` method would be a big `switch(state) {...` followed by `if (not last state) myHandler.post(this)` – pskink Aug 21 '17 at 11:05
  • ... or (i would personally do that) you can create a `Handler` and override its `handleMessage` method and instead of `post` i 'd call `sendEmptyMessage(0)` - no need for any `Runnable` - in this case `Message.what` would store your "step" – pskink Aug 21 '17 at 11:10
  • @pskink Yeah that also sounds good, looks a little better also. Basically in the `handleMessage`, after I do my work I call `this.sendEmptyMessage(msg.what + 1);` ? – Boby Aug 21 '17 at 12:06
  • exactly, of course with some `if` – pskink Aug 21 '17 at 12:15
  • Of course, and again, your help is very much appreciated. – Boby Aug 21 '17 at 12:20
  • ~"**I don't know why they must be initialised on the UI thread, but they must**". I'd contact the Ad company and ask them why... – IgorGanapolsky Sep 21 '17 at 14:10

4 Answers4

1

Well, Runnable inside your runOnUiThread is just operation in Main Thread.

Imagine that some simple action like

textView.setText("example");

will block Main Thread for 5 ms. Usually you will not see it.

Now imagine that you have like 1000 same operations for 5 seconds. And every blocks Main Thread for 5 ms. Simple calculating 5ms * 1000 = 5000ms = 5 seconds. So it will block Main Thread permamently. But if you have 10 operations you will block only 50 ms, in other words its just 1% of load that you will not feel.

So possible amount of calls depends on size of View, how hard render is and how fast is device.

P.S. For adepts of AsyncTask - there is no real difference between runOnUiThread and AsyncTask because those 1000 Runnables will execute in Main Thread by the same way.

Even if I do same thing inside onCreate method of Activity that will block UI hard

Andrey Danilov
  • 6,194
  • 5
  • 32
  • 56
  • Yeah but I'm calling the code from another thread, so it will be posted to message queue, also parts of that code might take even a second to execute, so UI thread might get blocked for even 10 seconds, in which case I will be getting an ANR. Now, the thing is that my app is actually an OpenGL game, and all rendering is done in the GL thread, so the game won't seem frozen for the user, but Android will detect that the game is frozen, so if between those Runnables, Android can still get input events everything will be ok since I actually don't care about rendering the UI on UI thread. – Boby Aug 21 '17 at 09:54
  • @Boby I guess you are using SurfaceView, which render in sepatate thread, so your MainThread cannot be blocked by render. So events like gsensor and others work separately – Andrey Danilov Aug 21 '17 at 09:58
  • @Boby but in your case you add more events than can execute so your queue is growing until app crash. – Andrey Danilov Aug 21 '17 at 10:00
  • Yes, exactly, so I'm now trying to figure out if gsensor like events can get processed between Runnables that I post to the UI thread if I just post them the classic way, one after another using activity.runOnUiThread(...) – Boby Aug 21 '17 at 10:01
  • Actually I'm not adding that many events, I'm just adding 10 events, but each one might take up to one second, so in case the UI thread doesn't handle other events between my Runnables it will get blocked for 10 seconds which will cause an ANR – Boby Aug 21 '17 at 10:04
  • @Boby do you spend entire time to render? Or there are some calculations too? – Andrey Danilov Aug 21 '17 at 10:04
  • You mean on the GL thread? Other things happen there but I'm not sure why it's relevant what happens on that thread. – Boby Aug 21 '17 at 10:08
  • @Boby you can parallel heavy calculations and that will speed up processing of event. If it cant help the last option is reducing events or reorginize rendering – Andrey Danilov Aug 21 '17 at 10:11
1

Yes. Runnable executing on UI thread will block main thread.

Check if below approach is useful for you.

  1. Create a Handler with Looper from Main :requestHandler
  2. Create a Handler with Looper for main thread : responseHandler and override handleMessage method
  3. post a Runnable task on requestHandler
  4. Inside Runnable task, call sendMessage on responseHandler
  5. This sendMessage result invocation of handleMessage in responseHandler.
  6. Get attributes from the Message and process it, update UI

Sample code:

    /* Handler */

    Handler requestHandler = new Handler(Looper.getMainLooper());

    final Handler responseHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            //txtView.setText((String) msg.obj);
            Toast.makeText(MainActivity.this,
                    "Adwork task is completed:"+(String)msg.obj,
                    Toast.LENGTH_LONG)
                    .show();
        }
    };

    for ( int i=0; i<10; i++) {
        // Start Adwork task
        Runnable myRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    /* Your business logic goes here */
                    // Send some result after computation
                    String text = "" + (++rId);
                    Message msg = new Message();

                    msg.obj = text.toString();
                    responseHandler.sendMessage(msg);
                    System.out.println(text.toString());

                } catch (Exception err) {
                    err.printStackTrace();
                }
            }
        };
        requestHandler.post(myRunnable);
    }

Useful articles:

handlerthreads-and-why-you-should-be-using-them-in-your-android-apps

android-looper-handler-handlerthread-i

Ravindra babu
  • 37,698
  • 11
  • 250
  • 211
0

Yes, you will feel animation will stop or start shutter if You run heavy operations in Ui thread. It's also depends of how fast device is.

What I would suggest you is to break your code in two parts. One that can be done in background and second that need Ui thread and execute them in AsyncTask.

Use doInBackgroud method to execute heavy operation and onPostExecute to update UI.

Note: If you break code into 1000 threads and run them in one moment, then probably You will hit device queue limit. (Break them into 10-50 parts. )

John Tribe
  • 1,407
  • 14
  • 26
  • The 1000 value was just an example, in actual code I have at most 10 Runnable objects that I want to run one after another on UI thread, and I want the UI thread to process other events between them. PS: I did not downvote. – Boby Aug 21 '17 at 08:37
  • "and I want the UI thread to process other events between them..." - you can runUiThread from backgroud task. activity.runOnUiThread(someAction), That way it will run this on ui from background task. upvote me :D – John Tribe Aug 21 '17 at 09:03
  • Yes, I know I can do that, but my question is, if I call activity.runOnUiThread(someAction) 10 times, will those 10 actions run one after another and block the UI thread until they all finish, or will the UI thread be able to process other events between those actions? – Boby Aug 21 '17 at 09:27
  • You can easily check this yourself. runOnUiThread( Thread.sleep(20000)). And see if you can do any operation. – John Tribe Aug 21 '17 at 09:41
  • There are limitations on having many AsyncTasks instantiated at once: https://stackoverflow.com/questions/43115714/how-many-parallel-execution-possible-using-executeonexecuter – IgorGanapolsky Sep 21 '17 at 19:23
0

What you are looking for is an AsyncTask.

These are designed to do some background processing, while the UI thread continues. It will NOT block the UI and will NOT cause ANR.

Within the AsyncTask, is an onPostExecute method, which allows you to post results back to the UI. So it is not completely detached from the UI Thread. And an onProgressUpdate for connection during the background processing

IAmGroot
  • 13,760
  • 18
  • 84
  • 154
  • asynctask are specifically built for short running operations – Sarthak Mittal Aug 21 '17 at 07:37
  • I'm trying to initialise some Ad networks, and if I initialise them on any other thread than the UI thread, app will crash(don't know why, but it does, and it also states in some of the networks' documentation that the initialisation must be done on UI thread), so AsyncTask is not good for me since it doesn't run on UI thread. – Boby Aug 21 '17 at 08:35
  • @Boby It depends what Ad networks you are referring to. But i imagine that they should provide specific examples for you to implement their ads. Note that network communication is not allowed on the UI thread. Else it will throw an exception. If you are just initializing objects, that then communicate properly, you should be ok. You can initialize your object in the async background and then attach it to the UI thread on finish. But I generally dont use async for creating UI objects. Again, check their specific examples they provided. – IAmGroot Aug 21 '17 at 09:20