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.
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 Thread
and 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 BroadcastReceivers
2. 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 factThread.sleep
causes problems in this scenario by "locking" the escalation.3Between Android 1.6 and 2.3, the scaling became parallel. Soon the
Thread.sleep
would not cause problems.3In 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:
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!!!
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.