Working with delay’s on Android. Best approach?

Asked

Viewed 3,288 times

7

Doubt

I recently needed the use of a delay in my application to run certain code, after some time.

Searching "over the internet", I found some "Gambi’s", and I found a somewhat new approach for me, which I tested and found very efficient (so far), which is this and it’s basically the following:

Handler handler = new Handler();
long delay = 1000; // tempo de delay em millisegundos
handler.postDelayed(new Runnable() {
    public void run() {
        // código a ser executado após o tempo de delay
    }
}, delay);

Question

As I do not know how this code works (internally), I can not know if it behaves well with Android, or if it can bring me problems in the future. Since implementations involving Thread manipulation should always have special attention.

  • Then I would like to know how it works (internally) and if it is efficient use it on Android?
  • Or if there is any better approach to this situation?

I’m open to changing my code to avoid future problems.

Note: The issue here is not to create a Timer, that runs at each time interval, but a code that waits a certain time to be executed later, not blocking the main thread of the application.

  • 2

    The right is to use Handler anyway. Do not use Sleep in Android applications.

3 answers

11


I’ve had the same question you’ve had, so I’m gonna risk an answer.

I believe it is better to start with a very important concept on Android, which is the Looper.

Looper

The Looper is a class that is used to handle messages using queue. That is, when a Thread want to receive messages from multiple threads and perform processing thread-safe, is a Looper to be used.

The Looper, together with the Handler basically transforms the Thread chain (I believe this is done using the method Thread.currentThread hopefully) on a pipeline, where messages are added to this pipeline, processed by Threadand released.1

This code snippet can help:

public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            // Prepara o looper na thread corrente     
            // a thread corrente vai ser detectada implicitamente
            Looper.prepare();

            // Agora, vamos automaticamente ligar o Handler
            // ao Looper que foi ligado a thread corrente
            // Você não precisar especificar o Looper explicitamente, ele saberá
            handler = new Handler();

            // Depois dessa linha, a Thread ira iniciar de fato
            // Rodando o loop de mensagens (Looper) e nao ira
            // terminar o loop ate que um problema ocorra ou 
            // voce chame o metodo quit() do Looper
            Looper.loop();
        } catch (Throwable t) {
            Log.e(TAG, "halted due to an error", t);
        } 
    }
}

Soon to handle the messages sent to that Thread, you can send a Runnable:

handler.post(new Runnable() {
    @Override
    public void run() {       
        // Isso sera feito no pipeline da Thread ligada a esse Handler
    }
});

Or post a message using obtainMessage and using the method sendMessage. Something like:

Message msg = handler.obtainMessage(what);
// Popular mensagem com dados
handler.sendMessage(msg);
// ou
msg.setTarget(handler);
msg.sendToTarget();

Of course you will have to implement a Handler overloading the method handleMessage to access the messages sent.

Well, that part I hope has been understood, now I will go to the part concerning the answer.

UI Thread or Main Thread

I believe you’ve heard enough, this is it Thread that Android performs all operations regarding the graphical interface and other related application features. Be modifying/inflating the layout, managing Activities (Which includes handling of touch events), Fragments, Services, ContentProviders and BroadcastReceivers2. Why access to these components should be made in Main Thread, because all this processing is not Thread-Safe, having a very large cost and risks to be (imagine if it happens a deadlock ?!?).

I think this one StackTrace that I generated can help visualize this:

java.lang.Arithmeticexception: divide by zero at android.app.Activitythread.performLaunchActivity(Activitythread.java:2198) at android.app.Activitythread.handleLaunchActivity(Activitythread.java:2257) at android.app.Activitythread.access$800(Activitythread.java:139) at android.app.Activitythread$H.handleMessage(Activitythread.java:1210) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.Activitythread.main(Activitythread.java:5086) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.Internal.os.Zygoteinit$Methodandargscaller.run(Zygoteinit.java:785) at com.android.Internal.os.Zygoteinit.main(Zygoteinit.java:601) at Dalvik.system.Nativestart.main(Native Method) Caused by: java.lang.Arithmeticexception: divide by zero at br.com.Planning.poker.app.activity.Customdeckactivity.onCreate(Customdeckactivity.java:82) at android.app.Activity.performCreate(Activity.java:5248) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110) at android.app.Activitythread.performLaunchActivity(Activitythread.java:2162)             at android.app.Activitythread.handleLaunchActivity(Activitythread.java:2257)             at android.app.Activitythread.access$800(Activitythread.java:139)             at android.app.Activitythread$H.handleMessage(Activitythread.java:1210)             at android.os.Handler.dispatchMessage(Handler.java:102)             at android.os.Looper.loop(Looper.java:136)             at android.app.Activitythread.main(Activitythread.java:5086)             at java.lang.reflect.Method.invokeNative(Native Method)             at java.lang.reflect.Method.invoke(Method.java:515)             at com.android.Internal.os.Zygoteinit$Methodandargscaller.run(Zygoteinit.java:785)             at com.android.Internal.os.Zygoteinit.main(Zygoteinit.java:601)             at Dalvik.system.Nativestart.main(Native Method)

You can see that to start/create my Activity (that generated this exception), he needed to send a message to the Looper of Main Thread (which is initiated by Zygote), to be sued.

Getting to the heart of your doubt, this code will not run outside the Main Thread*, precisely because you are sending a message to Main Thread, for IT to process, which will stop other processing (making your application not responsive to touch, generating ANR).

*I believe his background is Main Thread.

There may be other ways to do it but I always do it that way:

Handler handler = new Handler();

handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        new AsynctTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                // Processamento fora da Main Thread
                return null;
            }

            @Override
            protected void onPostExecute(Void result) {
                // Processamento na Main Thread (podendo alterar a UI)
            }
        }.execute();
    }
}, delay);

Regarding processing outside the Main Thread, there are also the Loaders that greatly facilitate the form of access to the local database and the notification of data availability. A tutorial I used to learn was: http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html. It is simpler and more than the officer, but it does not replace of course.

Thread Sleep vs Handler

Beyond the approach using Handler which is undoubtedly more advantageous, there is no loss of performance using Handler in Main Thread because the Looper has already been initialized. The class builder Handler seeks an instance of Looper in the ThreadLocal, soon there is no creation of the queues and nothing extra because that Looper is created by Zygote.

In the approach of AsyncTask there is a relatively high cost of creating a Thread just to use Thread.sleep and then run a processing.

If processing is done on background in the solution by Handler there is also the cost of creating a AsyncTask, but the enforcement order of Threads on Android.

  • Before Android 1.6, all the escalation of Threads was serial. Indeed the Thread.sleep causes problems in this scenario by "locking" the schedule.3

  • Between Android 1.6 and 2.3, the escalation became parallel. Logo o Thread.sleep wouldn’t cause problems.3

  • In android 3.0 the default scheduling was again serial (due to problems in parallelism), but it was introduced a way to scale in parallel using Executors, being a way to overcome the serial problem.3

Configuration change

There is a recurring problem in the use of AsyncTask and the life cycle of Activity/Fragment. The problem occurs when starting a AsyncTask and while its processing does not end happens the destruction or stop of the Activity. Soon when updating the UI at the end, it is not the same Activity and consequently memory Leak may occur. This post helps to visualize the problem and a solution: http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html

For this it is recommended the use of Loaders, which are automatically managed by virtue of the Activity.

I hope it was clear, even though I didn’t know how much you knew about these details, I tried to do the most comprehensive so that other people with this same doubt can take advantage.

Any detail is just say that I complement my answer.

References:

  1. http://mindtherobot.com/blog/159/android-guts-intro-to-loopers-and-handlers/
  2. https://stackoverflow.com/questions/3652560/what-is-the-android-uithread-ui-thread
  3. http://www.jayway.com/2012/11/28/is-androids-asynctask-executing-tasks-serially-or-concurrently/
  • There were some details about Thread what I will do soon.

  • I understood its placement (I think), in the case then the postDelayed of Handler, is efficient with Android, what it does is send a Runnable to be executed later, and in case the way he did it would execute the code of the Runnable in the main Thread, and in case you gave the suggestion that if I want to run in another Thread, not to block the main Thread of Android, if it is some time-consuming (heavy) task, I should create a AsynctTask in the Runnable. Right?

  • Exactly, soon (when I get to a computer) I will comment on a few more things to make the answer more complete.

  • Very, good your explanation, regarding Thread, especially the topic Thread Sleep vs Handler, which demonstrates the advantages and what happens in each approach.

2

Checking some native implementations of Android, to understand how it works and to be able to create some customizations in my implementation. I came across a native approach and attached to Android Object View, which provides an implementation of postDelayed, to be used in the Android Object View, giving the impression that this seems to be even the best approach to the use of delays in Android, follows the code of Android native view taken from Android source code:

public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
    return true;
}

From what you can see he already does some treatments, about Thread-safe, to avoid problems. A little more comprehensive than the implementation suggested in the question directly using the Handler Object:

Handler handler = new Handler();
long delay = 1000; // tempo de delay em millisegundos
handler.postDelayed(new Runnable() {
    public void run() {
        // código a ser executado após o tempo de delay
    }
}, delay);

So a better way to implement this code would be by using the method postDelayed of Object View:

long delay = 1000; // tempo de delay em millisegundos
instanciaDaView.postDelayed(new Runnable() {
    public void run() {
        // código a ser executado após o tempo de delay
    }
}, delay);

This approach is used in native Android classes, such as Abslistview I was studying

To my surprise this approach is already available from the API level 1 of Android, as demonstrated here in the documentation.

2

I advise you to use an Asynctask.

It would be interesting to do something like this:

new AsyncTask<String, Void, String>() {
            @Override
            protected void onPreExecute() {
               // aqui vc pode bloquear a execução por um tempo
               try {
                    Thread.sleep(5000);
                   } catch (InterruptedException e) {
                             e.printStackTrace();
                   } 
            }

            @Override
            protected String doInBackground(String... params) {
               // aqui vc pode bloquear a execução da thread
                     try {
                         Thread.sleep(5000);
                     } catch (InterruptedException e) {
                               e.printStackTrace();
                     } 
            }

            @Override
            protected void onPostExecute(String string) {
                // mais código aqui
            }
        }.execute();

This could run an operation after a time interval and will not block the main thread.

I hope I’ve helped!!!

  • You could put this code you posted inside the AsyncTask that will work, but I think it’s unnecessary to manipulate a Handle when you can do Thread.sleep(5000);

  • No problem using Thread.sleep(5000); on Android? Even if it’s in the background, I’ve read somewhere(I don’t remember) that Thread.sleep on Android is a mistake. For example, if my AsyncTask utilise SERIAL_EXECUTOR, if there are other threads, they will not be executed until it is finished. I don’t think it’s a good idea. Right?

  • Fernando: I’ve never read anything about it. I thought it’s okay to use, until pq I saw this being implemented in a google example app (more specifically, the GCM example app, in the Gcmintentservice.java class). So if there are controversies, I would like to know to be able to make better implementations! Thank you!!

  • In the wakim’s response the topic Thread Sleep vs Handler, illustrates this scenario.

  • I understand, I have no reputation to comment on there. Thread Sleep works for me because I’m programming on API 18, and as it was posted there, there are no problems with this approach on the most current Androids. Thank you all for clarifying that!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.