¿Cuál es el propósito de Looper y cómo usarlo?


454

Soy nuevo en android. Quiero saber qué hace la Looperclase y también cómo usarla. He leído la documentación de la clase Android Looper pero no puedo entenderla completamente. Lo he visto en muchos lugares pero no puedo entender su propósito. ¿Alguien puede ayudarme definiendo el propósito Loopery también dando un ejemplo simple si es posible?


77
Acabo de encontrar una explicación extraordinariamente completa y clara de Looper y su uso en Safari Books Online. Desafortunadamente, sospecho que el acceso es gratuito solo por un tiempo limitado. safaribooksonline.com/library/view/efficient-android-threading/…
Joe Lapp

1
Los artículos y las páginas de referencia de Android requieren que usted tenga y comprenda un artículo anterior, antes de poder comprender el actual. Le sugiero que lea los artículos de Actividad y Servicio en las guías de Api, y luego lea Handler and Looper. También ayuda si comprende qué es un hilo (no un hilo de Android, sino un hilo en general ... por ejemplo, POSIX).
FutureSci

1
Este artículo me pareció útil: codetheory.in/…
Herman

Respuestas:


396

¿Qué es looper?

Looper es una clase que se utiliza para ejecutar los mensajes (Runnables) en una cola. Los subprocesos normales no tienen esa cola, por ejemplo, el subproceso simple no tiene ninguna cola. Se ejecuta una vez y una vez que finaliza la ejecución del método, el hilo no ejecutará otro Mensaje (Ejecutable).

¿Dónde podemos usar la clase Looper?

Si alguien quiere ejecutar varios mensajes (Runnables), entonces debe usar la clase Looper que es responsable de crear una cola en el hilo. Por ejemplo, al escribir una aplicación que descarga archivos de Internet, podemos usar la clase Looper para colocar los archivos que se descargarán en la cola.

¿Cómo funciona?

Hay un prepare()método para preparar el Looper. Luego puede usar el loop()método para crear un bucle de mensajes en el hilo actual y ahora su Looper está listo para ejecutar las solicitudes en la cola hasta que salga del bucle.

Aquí está el código por el cual puede preparar el Looper.

class LooperThread extends Thread {
      public Handler mHandler;

      @Override
      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              @Override
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

17
Un AsyncTask es mejor para ese propósito y menos complejo, ya que encapsula toda la gestión de subprocesos.
Fernando Gallego

44
Debería tener anotaciones @Override antes de los métodos run () y handleMessage ()
Andrew Mackenzie

55
La documentación indica que debe llamar a looper.quit. En su código anterior, Looper.loop se bloqueará indefinidamente.
AndroidDev

2
Cómo salir de un bucle. Me refiero a dónde incluir Looper.quit () en el ejemplo de código anterior.
Seenu69

66
Creo que sería mejor usar HandlerThread, que es una clase conveniente para un hilo con un bucle.
Nimrod Dayan

287

Puede comprender mejor qué es Looper en el contexto del marco GUI. Looper está hecho para hacer 2 cosas.

1) Looper transforma un hilo normal , que termina cuando su método run () regresa, en algo que se ejecuta continuamente hasta que se ejecuta la aplicación de Android , lo cual es necesario en el marco de la GUI (Técnicamente, todavía termina cuando el método run () regresa. Pero déjame aclarar lo que quiero decir a continuación).

2) Looper proporciona una cola donde se ponen en cola los trabajos a realizar, que también es necesario en el marco de la GUI.

Como ya sabrá, cuando se inicia una aplicación, el sistema crea un subproceso de ejecución para la aplicación, denominado "principal", y las aplicaciones de Android normalmente se ejecutan completamente en un solo subproceso por defecto, el "subproceso principal". Pero el hilo principal no es un secreto, un hilo especial . Es solo un hilo normal similar a los hilos que creas con new Thread()código, lo que significa que termina cuando vuelve su método run (). Piense en el siguiente ejemplo.

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

Ahora, apliquemos este principio simple a las aplicaciones de Android. ¿Qué pasaría si una aplicación de Android se ejecuta en hilo normal? Un hilo llamado "main" o "UI" o lo que sea que inicie su aplicación, y dibuja toda la IU. Entonces, la primera pantalla se muestra a los usuarios. ¿Y ahora qué? El hilo principal termina? No, no debería. Debería esperar hasta que los usuarios hagan algo, ¿verdad? Pero, ¿cómo podemos lograr este comportamiento? Bueno, podemos intentarlo con Object.wait()oThread.sleep(). Por ejemplo, el subproceso principal finaliza su trabajo inicial para mostrar la primera pantalla y duerme. Se despierta, lo que significa interrumpido, cuando se busca un nuevo trabajo para hacer. Hasta ahora todo bien, pero en este momento necesitamos una estructura de datos similar a la cola para mantener múltiples trabajos. Piense en un caso en el que un usuario toca la pantalla en serie, y una tarea tarda más en terminar. Por lo tanto, necesitamos tener una estructura de datos para mantener los trabajos a realizar de primero en entrar, primero en salir. Además, puede imaginarse que implementar un subproceso que siempre se ejecuta y procesa el trabajo cuando se llega mediante la interrupción no es fácil y conduce a un código complejo y, a menudo, imposible de mantener. Preferimos crear un nuevo mecanismo para tal propósito, y de eso se trata Looper . El documento oficial de la clase Looper.dice: "Los subprocesos por defecto no tienen un bucle de mensajes asociado con ellos", y Looper es una clase "utilizada para ejecutar un bucle de mensajes para un subproceso". Ahora puedes entender lo que significa.

Para aclarar las cosas, verifiquemos el código donde se transforma el hilo principal. Todo sucede en la clase ActivityThread . En su método main (), puede encontrar el siguiente código, que convierte un hilo principal normal en algo que necesitamos.

public final class ActivityThread {
    ...
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        Looper.loop();
        ...
    }
}

y Looper.loop()bucle de método infinitamente y retirar un mensaje y procesar uno a la vez:

public static void loop() {
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
    }
}

Entonces, básicamente Looper es una clase hecha para abordar un problema que ocurre en el marco de la GUI. Pero este tipo de necesidades también puede ocurrir en otra situación. En realidad, es un patrón bastante famoso para la aplicación de subprocesos múltiples, y puede obtener más información al respecto en " Programación concurrente en Java " de Doug Lea (Especialmente, el capítulo 4.1.4 "Subprocesos de trabajo" sería útil). Además, puede imaginar que este tipo de mecanismo no es único en el marco de Android, pero todo el marco de la GUI puede necesitar algo similar a esto. Puede encontrar casi el mismo mecanismo en el marco de Java Swing.


26
Esta es la única respuesta que realmente explica algo acerca de por qué alguna vez se usaría la clase Looper. No estoy seguro de por qué no es la respuesta principal, las tres respuestas mejor calificadas no explican nada.
Andrew Koster,

44
@ALASKA. Es por eso que agregué esta respuesta, aunque parecía demasiado tarde. ¡Me alegra que mi respuesta te haya ayudado! :)
김준호

1
@ Hey-men-whatsup Sí
김준호

1
Antes de leer esto pensé "Looper ???" y ahora "Oh sí, hablemos". Gracias hombre, gran respuesta :)
umerk44

Pregunta rápida. Declaró que en el hilo principal después de que extrae todos los elementos de la interfaz de usuario, se pone a dormir. Pero digamos que el usuario interactúa con un botón en la pantalla, ¿no es ese clic de botón incluso puesto en una cola principal, luego algún objeto lo enviará a la actividad correcta, luego el hilo principal para esa actividad está despierto y se ejecutará ¿El código de la devolución de llamada para ese clic de botón?
CapturedTree

75

Looper permite que las tareas se ejecuten secuencialmente en un solo hilo. Y handler define aquellas tareas que necesitamos ejecutar. Es un escenario típico que estoy tratando de ilustrar en este ejemplo:

class SampleLooper extends Thread {
@Override
public void run() {
  try {
    // preparing a looper on current thread     
    // the current thread is being detected implicitly
    Looper.prepare();

    // now, the handler will automatically bind to the
    // Looper that is attached to the current thread
    // You don't need to specify the Looper explicitly
    handler = new Handler();

    // After the following line the thread will start
    // running the message loop and will not normally
    // exit the loop unless a problem happens or you
    // quit() the looper (see below)
    Looper.loop();
  } catch (Throwable t) {
    Log.e(TAG, "halted due to an error", t);
  } 
}
}

Ahora podemos usar el controlador en algunos otros hilos (digamos ui thread) para publicar la tarea en Looper para ejecutar.

handler.post(new Runnable()
{
public void run() {
//This will be executed on thread using Looper.
    }
});

En el hilo de la interfaz de usuario tenemos un Looper implícito que nos permite manejar los mensajes en el hilo de la interfaz de usuario.


no bloqueará ningún proceso de IU, ¿es cierto?
gumuruh

44
Gracias por incluir una muestra de cómo publicar "trabajos" en la cola
Peter Lillevold

Esto no explica por qué uno usaría esta clase, solo cómo.
Andrew Koster,

33

Android Looperes un envoltorio para unir MessageQueuea Thready gestiona la cola de procesamiento. Se ve muy críptico en la documentación de Android y muchas veces podemos enfrentar problemas Looperrelacionados con el acceso a la interfaz de usuario. Si no entendemos los conceptos básicos, se vuelve muy difícil de manejar.

Aquí hay un artículo que explica el Looperciclo de vida, cómo usarlo y su uso LooperenHandler

ingrese la descripción de la imagen aquí

Looper = Hilo + MessageQueue


3
Esto no explica por qué uno usaría esta clase, solo cómo.
Andrew Koster,

14

Definición de Looper & Handler:

Looper es una clase que convierte un subproceso en un subproceso y controlador de canalización le brinda un mecanismo para insertar tareas desde cualquier otro subproceso.

Detalles:

Por lo tanto, un hilo de PipeLine es un hilo que puede aceptar más tareas de otros hilos a través de un controlador.

El Looper se llama así porque implementa el bucle: toma la siguiente tarea, la ejecuta, luego toma la siguiente y así sucesivamente. El controlador se llama controlador porque se usa para manejar o aceptar la siguiente tarea cada vez desde cualquier otro subproceso y pasarlo a Looper (subproceso o subproceso de línea de tubería).

Ejemplo:

Un ejemplo perfecto de Looper and Handler o PipeLine Thread es descargar más de una imagen o subirlas a un servidor (Http) una por una en una sola secuencia en lugar de comenzar una nueva secuencia para cada llamada de red en segundo plano.

Lea más aquí sobre Looper and Handler y la definición de Pipeline Thread:

Android Guts: Introducción a Loopers y Handlers


7

Un Looper tiene un synchronized MessageQueueque se utiliza para procesar mensajes colocados en la cola.

Implementa un Threadpatrón de almacenamiento específico.

Solo uno Looperpor Thread. Los métodos clave incluyen prepare(), loop()y quit().

prepare()inicializa la corriente Threadcomo a Looper. prepare()es un staticmétodo que usa la ThreadLocalclase como se muestra a continuación.

   public static void prepare(){
       ...
       sThreadLocal.set
       (new Looper());
   }
  1. prepare() debe llamarse explícitamente antes de ejecutar el bucle de eventos.
  2. loop()ejecuta el bucle de eventos que espera a que lleguen los mensajes en la cola de mensajes de un subproceso específico. Una vez que se recibe el siguiente mensaje, el loop()método envía el mensaje a su controlador de destino
  3. quit()cierra el bucle de eventos. No termina el ciclo, sino que pone en cola un mensaje especial

Looperse puede programar en Threadvarios pasos

  1. Ampliar Thread

  2. Llame Looper.prepare()para inicializar Thread como unLooper

  3. Cree uno o más Handlerpara procesar los mensajes entrantes

  4. Llame Looper.loop()para procesar mensajes hasta que se indique el bucle quit().

5

La vida útil de Java Thread termina después de completar el run()método. El mismo hilo no se puede iniciar de nuevo.

Looper transforma lo normal Threaden un bucle de mensajes. Los métodos clave de Looperson:

void prepare ()

Inicialice el hilo actual como un looper. Esto le da la oportunidad de crear controladores que luego hagan referencia a este looper, antes de comenzar realmente el ciclo. Asegúrese de llamar a loop () después de llamar a este método, y finalizar llamando a quit ().

void loop ()

Ejecute la cola de mensajes en este hilo. Asegúrese de llamar a quit () para finalizar el ciclo.

void quit()

Deja el looper.

Hace que el método loop () termine sin procesar más mensajes en la cola de mensajes.

Este artículo de Mindorks de Janishar explica los conceptos básicos de una manera agradable.

ingrese la descripción de la imagen aquí

Looperestá asociado con un hilo. Si necesita Looperun subproceso de interfaz de usuario, Looper.getMainLooper()devolverá el subproceso asociado.

Debe Looperestar asociado con un controlador .

Looper, Handlery HandlerThreadson la forma en que Android soluciona los problemas de programación asincrónica.

Una vez que lo haya hecho Handler, puede llamar a las siguientes API.

post (Runnable r)

Hace que el Runnable r se agregue a la cola de mensajes. El ejecutable se ejecutará en el hilo al que está conectado este controlador.

boolean sendMessage (Message msg)

Introduce un mensaje en el final de la cola de mensajes después de todos los mensajes pendientes antes de la hora actual. Se recibirá en handleMessage (Message), en el hilo adjunto a este controlador.

HandlerThread es una clase útil para comenzar un nuevo hilo que tiene un looper. El looper se puede usar para crear clases de manejador

En algunos escenarios, no puede ejecutar Runnabletareas en UI Thread. Por ejemplo, operaciones de red: envíe un mensaje en un socket, abra una URL y obtenga contenido leyendoInputStream

En estos casos, HandlerThreades útil. Puede obtener un Looperobjeto HandlerThready crear un subproceso Handleren HandlerThreadlugar del hilo principal.

El código de HandlerThread será así:

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

Consulte la publicación a continuación para obtener un código de ejemplo:

Android: tostadas en un hilo


5

Entendiendo los hilos Looper

Un Java Thread una unidad de ejecución que fue diseñada para realizar una tarea en su método run () y terminar después de eso: ingrese la descripción de la imagen aquí

Pero en Android hay muchos casos de uso en los que necesitamos mantener vivo un hilo y esperar las entradas / eventos del usuario, por ejemplo. UI thread akaMain Thread .

El subproceso principal en Android es un subproceso de Java que JVM inició por primera vez en el lanzamiento de una aplicación y continúa ejecutándose hasta que el usuario elige cerrarlo o encuentra una excepción no controlada.

Cuando se inicia una aplicación, el sistema crea un hilo de ejecución para la aplicación, llamado "main". Este hilo es muy importante porque se encarga de enviar eventos a los widgets de interfaz de usuario apropiados, incluidos los eventos de dibujo.

ingrese la descripción de la imagen aquí

Ahora, el punto a tener en cuenta aquí es que aunque el hilo principal es el hilo de Java, sigue escuchando los eventos del usuario y dibuja cuadros de 60 fps en la pantalla y aún así no morirá después de cada ciclo. como es asi

La respuesta es la clase Looper : Looper es una clase que se utiliza para mantener vivo un hilo y administrar una cola de mensajes para ejecutar tareas en ese hilo.

Los subprocesos por defecto no tienen un bucle de mensajes asociado, pero puede asignar uno llamando a Looper.prepare () en el método de ejecución y luego llamando a Looper.loop ().

El propósito de Looper es mantener vivo un subproceso y esperar al próximo ciclo del Messageobjeto de entrada para realizar el cálculo, que de lo contrario se destruirá después del primer ciclo de ejecución.

Si desea profundizar en cómo Looper gestiona la Messagecola de objetos, puede echar un vistazo al código fuente de Looperclass:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/os/Looper.java

A continuación se muestra un ejemplo de cómo puede crear una clase Looper Thready comunicarse con ella ActivityutilizandoLocalBroadcast

class LooperThread : Thread() {

    // sendMessage success result on UI
    private fun sendServerResult(result: String) {
        val resultIntent = Intent(ServerService.ACTION)
        resultIntent.putExtra(ServerService.RESULT_CODE, Activity.RESULT_OK)
        resultIntent.putExtra(ServerService.RESULT_VALUE, result)
        LocalBroadcastManager.getInstance(AppController.getAppController()).sendBroadcast(resultIntent)
    }

    override fun run() {
        val looperIsNotPreparedInCurrentThread = Looper.myLooper() == null

        // Prepare Looper if not already prepared
        if (looperIsNotPreparedInCurrentThread) {
            Looper.prepare()
        }

        // Create a handler to handle messaged from Activity
        handler = Handler(Handler.Callback { message ->
            // Messages sent to Looper thread will be visible here
            Log.e(TAG, "Received Message" + message.data.toString())

            //message from Activity
            val result = message.data.getString(MainActivity.BUNDLE_KEY)

            // Send Result Back to activity
            sendServerResult(result)
            true
        })

        // Keep on looping till new messages arrive
        if (looperIsNotPreparedInCurrentThread) {
            Looper.loop()
        }
    }

    //Create and send a new  message to looper
    fun sendMessage(messageToSend: String) {
        //Create and post a new message to handler
        handler!!.sendMessage(createMessage(messageToSend))
    }


    // Bundle Data in message object
    private fun createMessage(messageToSend: String): Message {
        val message = Message()
        val bundle = Bundle()
        bundle.putString(MainActivity.BUNDLE_KEY, messageToSend)
        message.data = bundle
        return message
    }

    companion object {
        var handler: Handler? = null // in Android Handler should be static or leaks might occur
        private val TAG = javaClass.simpleName

    }
}

Uso :

 class MainActivity : AppCompatActivity() {

    private var looperThread: LooperThread? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // start looper thread
        startLooperThread()

        // Send messages to Looper Thread
        sendMessage.setOnClickListener {

            // send random messages to looper thread
            val messageToSend = "" + Math.random()

            // post message
            looperThread!!.sendMessage(messageToSend)

        }   
    }

    override fun onResume() {
        super.onResume()

        //Register to Server Service callback
        val filterServer = IntentFilter(ServerService.ACTION)
        LocalBroadcastManager.getInstance(this).registerReceiver(serverReceiver, filterServer)

    }

    override fun onPause() {
        super.onPause()

        //Stop Server service callbacks
     LocalBroadcastManager.getInstance(this).unregisterReceiver(serverReceiver)
    }


    // Define the callback for what to do when data is received
    private val serverReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val resultCode = intent.getIntExtra(ServerService.RESULT_CODE, Activity.RESULT_CANCELED)
            if (resultCode == Activity.RESULT_OK) {
                val resultValue = intent.getStringExtra(ServerService.RESULT_VALUE)
                Log.e(MainActivity.TAG, "Server result : $resultValue")

                serverOutput.text =
                        (serverOutput.text.toString()
                                + "\n"
                                + "Received : " + resultValue)

                serverScrollView.post( { serverScrollView.fullScroll(View.FOCUS_DOWN) })
            }
        }
    }

    private fun startLooperThread() {

        // create and start a new LooperThread
        looperThread = LooperThread()
        looperThread!!.name = "Main Looper Thread"
        looperThread!!.start()

    }

    companion object {
        val BUNDLE_KEY = "handlerMsgBundle"
        private val TAG = javaClass.simpleName
    }
}

¿Podemos utilizar la tarea Async o los servicios de intención en su lugar?

  • Las tareas asíncronas están diseñadas para realizar una operación corta en segundo plano y proporcionar progreso y resultados en el hilo de la interfaz de usuario. Las tareas asíncronas tienen límites, como no puede crear más de 128 tareas asíncronas y ThreadPoolExecutorsolo permitirá hasta 5 tareas asíncronas .

  • IntentServicestambién están diseñados para realizar tareas en segundo plano durante un poco más de tiempo y puede usar LocalBroadcastpara comunicarse Activity. Pero los servicios se destruyen después de la ejecución de la tarea. Si desea mantenerlo funcionando durante mucho tiempo, entonces necesita hacer diablos como while(true){...}.

Otros casos de uso significativos para Looper Thread:

  • Se utiliza para la comunicación de socket de 2 vías donde el servidor sigue escuchando el socket del cliente y confirma el acuse de recibo

  • Procesamiento de mapa de bits en segundo plano. Pase la url de la imagen al hilo Looper y aplicará efectos de filtro y la almacenará en una ubicación temporal y luego transmitirá la ruta temporal de la imagen.


4

Esta respuesta no tiene nada que ver con la pregunta, pero el uso de looper y la forma en que las personas crearon el controlador y el looper en TODAS las respuestas aquí son simplemente una mala práctica (aunque algunas explicaciones son correctas), tengo que publicar esto:

HandlerThread thread = new HandlerThread(threadName);
thread.start();
Looper looper = thread.getLooper();
Handler myHandler = new Handler(looper);

y para una implementación completa


3

Manejar múltiples elementos hacia abajo o subir en un Servicio es un mejor ejemplo.

Handlery a AsnycTaskmenudo se usan para propagar Eventos / Mensajes entre la UI (hilo) y un hilo de trabajo o para retrasar acciones. Por lo tanto, están más relacionados con la interfaz de usuario.

A Loopermaneja tareas ( Runnables, Futures ) en una cola relacionada con subprocesos en segundo plano, incluso sin interacción del usuario o una IU mostrada (la aplicación descarga un archivo en segundo plano durante una llamada).


1

¿Qué es looper?

DE DOCUMENTOS

Looper

LooperClase utilizada para ejecutar un bucle de mensajes para a thread. Los subprocesos por defecto no tienen un bucle de mensaje asociado con ellos; para crear uno, llame prepare()al hilo que ejecutará el bucle y luego haga loop()que procese los mensajes hasta que se detenga.

  • A Looperes un ciclo de manejo de mensajes:
  • Un personaje importante de Looper es que está asociado con el hilo dentro del cual se crea el Looper
  • La clase Looper mantiene a MessageQueue, que contiene una lista de mensajes. Un personaje importante de Looper es que está asociado con el hilo dentro del cual se crea el Looper.
  • Se Looperllama así porque implementa el bucle: toma la siguiente tarea, la ejecuta, luego toma la siguiente y así sucesivamente. El Handlerse llama controlador porque alguien no podría inventar un nombre mejor
  • Android Looperes una clase Java dentro de la interfaz de usuario de Android que, junto con la clase Handler, procesa eventos de IU, como clics de botones, redibujos de pantalla e interruptores de orientación.

¿Cómo funciona?

ingrese la descripción de la imagen aquí

Creando Looper

Un hilo consigue un Loopery MessageQueuellamando Looper.prepare()después de su ejecución. Looper.prepare()identifica el hilo de llamada, crea un Looper y un MessageQueueobjeto y asocia el hilo

CÓDIGO DE MUESTRA

class MyLooperThread extends Thread {

      public Handler mHandler; 

      public void run() { 

          // preparing a looper on current thread  
          Looper.prepare();

          mHandler = new Handler() { 
              public void handleMessage(Message msg) { 
                 // process incoming messages here
                 // this will run in non-ui/background thread
              } 
          }; 

          Looper.loop();
      } 
  }

Para obtener más información, consulte la siguiente publicación


excelente, he entendido la mecánica. gracias
Serg Burlaka
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.