Conceptos básicos de Android: ejecutar código en el hilo de la interfaz de usuario

450

En el punto de vista del código en ejecución en el hilo de la interfaz de usuario, ¿hay alguna diferencia entre:

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

o

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

y

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Luky
fuente
Para aclarar mi pregunta: supuse que esos códigos se llamaban desde un hilo de servicio, generalmente un oyente. También supuse que hay un trabajo pesado que realizar en la función doInBackground () de AsynkTask o en una nueva Tarea (...) llamada antes de los dos primeros fragmentos. De todos modos, el onPostExecute () de AsyncTask se coloca al final de la cola del evento, ¿verdad?
Luky

Respuestas:

288

Ninguno de ellos es exactamente el mismo, aunque todos tendrán el mismo efecto neto.

La diferencia entre el primero y el segundo es que si se encuentra en el hilo principal de la aplicación al ejecutar el código, el primero ( runOnUiThread()) ejecutará el Runnableinmediatamente. El segundo ( post()) siempre coloca el Runnableal final de la cola de eventos, incluso si ya está en el hilo principal de la aplicación.

El tercero, suponiendo que cree y ejecute una instancia de BackgroundTask, perderá mucho tiempo sacando un subproceso del grupo de subprocesos, para ejecutar un no operativo predeterminado doInBackground(), antes de hacer lo que equivale a a post(). Este es, con mucho, el menos eficiente de los tres. Úselo AsyncTasksi realmente tiene trabajo que hacer en un hilo de fondo, no solo para el uso de onPostExecute().

CommonsWare
fuente
27
También tenga en cuenta que AsyncTask.execute()requiere que llame desde el hilo de la interfaz de usuario de todos modos, lo que hace que esta opción sea inútil para el caso de usar simplemente ejecutar código en el hilo de la interfaz de usuario desde un hilo de fondo a menos que mueva todo su trabajo de fondo doInBackground()y lo use AsyncTaskcorrectamente.
kabuko
@kabuko, ¿cómo puedo verificar que estoy llamando AsyncTaskdesde el hilo de la interfaz de usuario?
Neil Galiaskarov
@NeilGaliaskarov Esto parece una opción sólida: stackoverflow.com/a/7897562/1839500
Dick Lucas el
1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
prohibición de geoingeniería
1
@NeilGaliaskarov para versiones mayores o iguales al uso de MLooper.getMainLooper().isCurrentThread
Balu Sangem
252

Me gusta el comentario de HPP , se puede usar en cualquier lugar sin ningún parámetro:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
pomber
fuente
2
¿Qué tan eficiente es esto? ¿Es lo mismo que otras opciones?
EmmanuelMess
59

Hay una cuarta forma de usar Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
vasart
fuente
56
Deberías tener cuidado con esto. Porque si crea un controlador en un hilo sin interfaz de usuario, publicará mensajes en el hilo sin interfaz de usuario. Un controlador de forma predeterminada envía un mensaje al hilo donde se crea.
lujop
108
ejecutar en el hilo principal de la interfaz de usuario do new Handler(Looper.getMainLooper()).post(r), que es la forma preferida de Looper.getMainLooper()hacer una llamada estática a main, mientras que postOnUiThread()debe tener una instancia de MainActivityalcance.
HPP
1
@HPP No conocía este método, será una excelente manera cuando no tenga ni Actividad ni Vista. ¡Funciona genial! muchas gracias muchisimas!
Sulfkain
@lujop Igual es el caso con el método de devolución de llamada onPreExecute de AsyncTask.
Sreekanth Karumanaghat
18

La respuesta de Pomber es aceptable, sin embargo, no soy un gran admirador de crear nuevos objetos repetidamente. Las mejores soluciones son siempre las que intentan mitigar el consumo de memoria. Sí, hay recolección automática de basura, pero la conservación de la memoria en un dispositivo móvil cae dentro de los límites de las mejores prácticas. El siguiente código actualiza TextView en un servicio.

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;
    }

}

Se puede usar desde cualquier lugar como este:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Joe
fuente
77
+1 por mencionar GC y creación de objetos. SIN EMBARGO, no estoy necesariamente de acuerdo The best solutions are always the ones that try to mitigate memory hog. Hay muchos otros criterios para best, y esto tiene un ligero olor a premature optimization. Es decir, a menos que sepa que lo está llamando lo suficiente como para que el número de objetos creados sea un problema (en comparación con las otras diez mil formas en que su aplicación probablemente está creando basura), lo que puede ser bestes escribir el más simple (más fácil de entender ), y pasar a otra tarea.
ToolmakerSteve
2
Por cierto, textViewUpdaterHandlersería mejor nombrar algo como uiHandlero mainHandler, ya que generalmente es útil para cualquier publicación en el hilo principal de la interfaz de usuario; no está en absoluto vinculado a su clase TextViewUpdater. Lo alejaría del resto de ese código y dejaría en claro que se puede usar en otro lugar ... El resto del código es dudoso, porque para evitar crear un objeto dinámicamente, divide lo que podría ser una sola llamada dos pasos setTexty postque dependen de un objeto de larga duración que se usa como temporal. Complejidad innecesaria y no segura para subprocesos. No es fácil de mantener.
ToolmakerSteve
Si realmente tiene una situación donde esto se llama tantas veces que vale la pena el almacenamiento en caché uiHandlery textViewUpdater, a continuación, mejorar su clase cambiando a public void setText(String txt, Handler uiHandler)y añadiendo la línea método uiHandler.post(this); Luego la persona que llama puede hacer en un solo paso: textViewUpdater.setText("Hello", uiHandler);. Luego, en el futuro, si necesita ser seguro para subprocesos, el método puede envolver sus declaraciones dentro de un bloqueo uiHandlery la persona que llama permanece sin cambios.
ToolmakerSteve
Estoy bastante seguro de que solo puedes ejecutar un Runnable una vez. Lo cual rompe tu idea, lo cual es bueno en general.
Nativ
@Nativ Nope, Runnable se puede ejecutar varias veces. Un hilo no puede.
Zbyszek
8

A partir de Android P puedes usar getMainExecutor():

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

De los documentos para desarrolladores de Android :

Devuelva un ejecutor que ejecutará tareas en cola en el hilo principal asociado con este contexto. Este es el hilo utilizado para enviar llamadas a los componentes de la aplicación (actividades, servicios, etc.).

Del CommonsBlog :

Puede llamar a getMainExecutor () en contexto para obtener un ejecutor que ejecutará sus trabajos en el hilo principal de la aplicación. Hay otras formas de lograr esto, usando Looper y una implementación personalizada de Executor, pero esto es más simple.

Dick Lucas
fuente
7

Si necesita usar Fragment, debe usar

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");
        }
    });

en vez de

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

Porque habrá una excepción de puntero nulo en alguna situación como fragmento de buscapersonas

Beyaz
fuente
1

Hola chicos, esta es una pregunta básica.

utilizar Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
raghu kambaa
fuente
¿Hay alguna razón por la que elegiste usar el controlador de propósito general sobre runOnUiThread?
ThePartyTurtle
Esto dará un java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()si no se llama desde el hilo de la interfaz de usuario.
Roc Boronat