Working with delay's on Android. Best approach?

Doubt

I recently needed to use a delay in my application to execute certain code after some time.

Searching "for the internet's", I found some "Gambi's", and I found a somewhat new approach to me, which I tested and found very efficient (so far), which is this and is 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 don't know how this code works (internally), I can't tell if this behaves well with Android, or if it might bring me problems in the future. Since implementations that involve thread manipulation should always pay special attention.

  • so I would like to know how it works (internally) and whether it is efficient to use it on Android?
  • or is there a better approach to this situation?

I am open to changing my code to avoid problems future.

Note: The question here is not to create a Timer, which runs every time interval, but rather a code that waits a certain time to then run, not blocking the main Thread of the application.

Author: Comunidade, 2014-06-27

3 answers

I've had the same doubt you had, so I'll risk an answer.

I think it's best to start with a very important concept on Android, which is Looper.

Looper

Looper is a class that is used for handling messages using queue. That is, when a Thread wants to receive messages from multiple threads and perform thread-safe processing, it is aLooper that should be used.

The Looper, along with the Handler basically transform the Thread current (I believe this is done using the Thread.currentThread method I hope) in a pipeline, where messages are added to that 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 messages sent to this 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 the obtainMessage and using the sendMessage method. Somewhat 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 handleMessage method to access the Sent Messages.

Well, this part I hope has been understood, now I will go to the part relating to the answer.

UI Thread or Main Thread

I believe you have heard a lot, it is in this Thread that Android performs all operations regarding the graphical interface and others related to application features. I.e. modifying / inflating the layout, managing Activities (which includes handling touch events), Fragments, Services, ContentProviders e BroadcastReceivers2. That's why access to these components should be done in Main Thread, since all this processing is not Thread-Safe, having a very large cost and risks of being (imagine if a deadlock happens ?!?).

I think this StackTrace 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 Met MethodAndArgsCaller.run(ZygoteInit.java:785) at com.android.internal.os.Zygoteinite.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 Met MethodAndArgsCaller.run(ZygoteInit.java:785)             at com.android.internal.os.Zygoteinite.main (ZygoteInit.java:601)             at dalvik.system.NativeStart.main (Native Method)

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

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

*I believe his context is Main Thread.

There may be other ways to do it but I always do it this 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);

With respect to processing outside of Main Thread, there are also Loaders that they greatly facilitate the way to access the local bank and the notification of the availability of data. A tutorial I used to learn was: http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html . he is simpler and more than the official, but does not replace of course.

Thread Sleep vs Handler

In addition to the approach using Handler which is undoubtedly more advantageous, there is no loss of performance using Handler in Main Thread because Looper has already been initialized earlier. The class constructorHandler it searches for an instance of Looper in ThreadLocal, so there is no queue creation and nothing extra because this Looper is created by Zygote.

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

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

  • Prior to Android 1.6, all Threads scaling was serial. In fact Thread.sleep causes problems in this scenario by "locking" the escalation.3

  • Between Android 1.6 and 2.3, the scaling became parallel. Soon the Thread.sleep would not cause problems.3

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

Setting change

There is a recurring problem with the use of AsyncTask and the life cycle of the Activity/Fragment. The problem occurs when starting a AsyncTask and while its processing does not end, the destruction or stop of the Activity happens. Soon when updating the UI at the end, it is not the same Activity and consequently memory leak may occur. It 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 to use Loaders, which are automatically managed by virtue of the life cycle of Activity.

I hope it was clear, although I do not know how much you knew of these details, I tried to do the most comprehensive so that other people with this same doubt can enjoy.

Any detail is just talk 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/
 11
Author: Wakim, 2017-05-23 12:37:33

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 vc could perform an operation after a time interval and will not block the main Thread.

Hope I helped!!!

 2
Author: Ighor Augusto, 2014-06-27 13:55:37

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

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 it already does some treatments, the thread-safe fence, to avoid problems. A little more comprehensive than the implementation suggested in the question using the Handler object directly:

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 postDelayed method of The View object:

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 the AbsListView that I was studying

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

 2
Author: Fernando Leal, 2015-10-15 20:34:53