android - example - Handlers, MessageQueue, Looper, ¿se ejecutan todos en el hilo de UI?
service handler android (4)
Estoy tratando de entender el tema y sé que puedo usar un Handler
para publicar mensajes / ejecutables en MessageQueue
, que a su vez es recogido por el Looper
y devuelto al manejador para su procesamiento.
Si MessageQueue
en un controlador en mi actividad, ¿la Activity
, MessageQueue
, MessageQueue
y Looper
ejecutan en el hilo de UI? Si no, ¿podría alguien explicar cómo se une todo esto? :)
Si publico en un controlador en mi actividad, ¿la actividad, controlador, MessageQueue y Looper se ejecutan en el hilo de UI? Si no, ¿podría alguien explicar cómo se une todo esto? :)
Depende de cómo crees Handler
Caso 1:
Handler()
El constructor predeterminado asocia este controlador con el Looper para el hilo actual.
Si crea Handler
esta manera en el hilo de UI, Handler
se asocia con Looper
de UI Thread. MessageQueue
también está asociado con Looper
con UI Thread.
Caso 2
Handler (Looper looper)
Use el Looper proporcionado en lugar del predeterminado.
Si creo un HandlerThread y paso el Looper de HandlerThread a Handler, Handler y Looper están asociados con HandlerThread y no con UI Thread. Handler
, MessageQueue
y Looper
están asociados con HandlerThread
.
Caso de uso: Desea ejecutar una operación de red o IO. No puede ejecutarlo en UI Thread y, por HandlerThread
tanto, HandlerThread
es útil para usted.
HandlerThread handlerThread = new HandlerThread("NetworkOperation");
handlerThread.start();
Handler requestHandler = new Handler(handlerThread.getLooper());
Si desea pasar datos de HandlerThread a UI Thread, puede crear otro Handler (por ejemplo, responseHandler) con Looper
desde UI Thread y llamar sendMessage
. El handleMessage
responseHandler
subproceso UI debe prevalecer sobre handleMessage
Consulte estas publicaciones para obtener más detalles.
¿Cuál es el propósito de Looper y cómo usarlo? (Para conceptos)
Android: brindis en un hilo (por ejemplo, código al vincular todos estos conceptos)
Intento implementar estas interfaces yo solo para entender el concepto. Por simplicidad, solo usa la interfaz por necesidad. Aquí está mi código de prueba:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class TestLooper {
public static void main(String[] args) {
UIThread thread = new UIThread();
thread.start();
Handler mHandler = new Handler(thread.looper);
new WorkThread(mHandler, "out thread").run();
}
}
class Looper {
private BlockingQueue<Message> message_list = new LinkedBlockingQueue<Message>();
public void loop() {
try {
while (!Thread.interrupted()) {
Message m = message_list.take();
m.exeute();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void insertMessage(Message msg) {
message_list.add(msg);
}
}
class Message {
String data;
Handler handler;
public Message(Handler handler) {
this.handler = handler;
}
public void setData(String data) {
this.data = data;
}
public void exeute() {
handler.handleMessage(this);
}
}
class Handler {
Looper looper;
public Handler(Looper looper) {
this.looper = looper;
}
public void dispatchMessage(Message msg) {
System.out.println("Handler dispatchMessage" + Thread.currentThread());
looper.insertMessage(msg);
}
public Message obtainMessage() {
return new Message(this);
}
public void handleMessage(Message m) {
System.out.println("handleMessage:" + m.data + Thread.currentThread());
}
}
class WorkThread extends Thread {
Handler handler;
String tag;
public WorkThread(Handler handler, String tag) {
this.handler = handler;
this.tag = tag;
}
public void run() {
System.out.println("WorkThread run" + Thread.currentThread());
Message m = handler.obtainMessage();
m.setData("message " + tag);
handler.dispatchMessage(m);
}
}
class UIThread extends Thread {
public Looper looper = new Looper();
public void run() {
//create handler in ui thread
Handler mHandler = new Handler(looper);
new WorkThread(mHandler, "inter thread").run();
System.out.println("thead run" + Thread.currentThread());
looper.loop();
}
}
Siguiendo con la parte de "cómo todo se une". Como escribió el usuario634618, el looper toma el hilo, el hilo principal de la IU en el caso del Looper
principal de una aplicación.
-
Looper.loop()
saca mensajes de su cola de mensajes. Cada Mensaje tiene una referencia a un Manejador asociado que debe devolverse a (el miembro de destino). - Dentro de
Looper.loop()
por cada mensaje obtenido de la cola:-
loop()
llama apublic void Handler.dispatchMessage(Message msg)
utilizando el Handler que se almacena en el mensaje como su miembro de destino. - Si el mensaje tiene un miembro de devolución de llamada Runnable, se ejecuta.
- De lo contrario, si el controlador tiene un conjunto de devolución de llamada compartido, se ejecuta.
- De lo contrario,
handleMessage()
Handler se llama con el mensaje como argumento. (Tenga en cuenta que si subclase Handler como AsyncTask lo hace, puede anularhandleMessage()
como lo hace).
-
En su pregunta acerca de que todos los objetos colaboradores están en el mismo hilo de interfaz de usuario, se debe crear un Handler
en el mismo hilo que el Looper
que enviará mensajes. Su constructor buscará el Looper
actual y lo almacenará como miembro, vinculando el Handler
a ese Looper
. También hará referencia a la cola de mensajes de Looper
directamente en su propio miembro. El Handler
se puede utilizar para enviar trabajo al Looper
desde cualquier hilo, pero esta identidad de las colas de mensajes enruta el trabajo para que se realice en el hilo de Looper
.
Cuando estamos ejecutando algún código en otro hilo y queremos enviar un Runnable para ser ejecutado en el hilo de UI, podemos hacerlo así:
// h is a Handler that we constructed on the UI thread.
public void run_on_ui_thread(final Handler h, final Runnable r)
{
// Associate a Message with our Handler and set the Message''s
// callback member to our Runnable:
final Message message = Message.obtain(h, r);
// The target is the Handler, so this asks our Handler to put
// the Message in its message queue, which is the exact same
// message queue associated with the Looper on the thread on
// which the Handler was created:
message.sendToTarget();
}
Respuesta corta: todos ejecutan en el mismo hilo. Si se crea una instancia a partir de una devolución de llamada del ciclo de vida de la Activity
, todos se ejecutan en el hilo de la interfaz de usuario principal.
Respuesta larga:
Un hilo puede tener un Looper
, que contiene un MessageQueue
. Para usar esta instalación, debería crear un Looper
en el hilo actual llamando (lo estático) Looper.prepare()
, y luego iniciar el ciclo llamando (también estático) Looper.loop()
. Estos son estáticos porque solo se supone que hay un Looper
por hilo.
La llamada a loop()
generalmente no retorna durante un tiempo, pero sigue sacando mensajes ("tareas", "comandos" o lo que quiera llamar) del MessageQueue
y los maneja individualmente (por ejemplo, devolviendo una Runnable
contenida) en el mensaje). Cuando no quedan mensajes en la cola, el hilo bloquea hasta que haya nuevos mensajes. Para detener un Looper
, debe llamar a quit()
en él (lo que probablemente no detiene el ciclo inmediatamente, sino que establece un indicador privado que se marca periódicamente desde el ciclo, indicándole que se detenga).
Sin embargo, no puede agregar mensajes a la cola directamente. En su lugar, registra un MessageQueue.IdleHandler
para esperar una devolución de llamada queueIdle()
, en la que puede decidir si desea hacer algo o no.Todos los controladores se llaman a su vez. (Por lo tanto, la "cola" no es realmente una cola, sino una colección de devoluciones de llamadas para ser llamadas regularmente ).
Nota con respecto al párrafo anterior: Esto realmente lo adiviné. No pude encontrar ninguna documentación sobre eso, pero tendría sentido.
actualización: vea el comentario de ahcox y su respuesta .
Debido a que esto es mucho trabajo, el marco proporciona la clase Handler
para simplificar las cosas . Cuando crea una instancia de Handler
, está (por defecto) vinculada al Looper
ya conectado al hilo actual. (El Handler
sabe a qué Looper
debe conectar porque llamamos a prepare()
anteriormente, lo que probablemente almacenaba una referencia al Looper
en un ThreadLocal
).
Con un Handler
, puede simplemente llamar a post()
para "poner un mensaje en la cola de mensajes del hilo" (por así decirlo). El Handler
se encargará de todas las cosas de devolución de llamada de IdleHandler
y se asegurará de que se ejecute Runnable
publicado. (También podría verificar si es el momento adecuado, si ha publicado con retraso).
Para que quede claro: la única forma de hacer que un hilo de bucle haga algo es publicar un mensaje en su ciclo. Esto es válido hasta que llame a quit () en el looper.
En cuanto al hilo de la interfaz de usuario de Android: en algún momento (probablemente antes de que se creen actividades similares) el marco ha configurado un Looper
(que contiene un MessageQueue
) y lo ha iniciado. A partir de este punto, todo lo que sucede en el hilo de la interfaz de usuario pasa por ese ciclo. Esto incluye la gestión del ciclo de vida de la actividad, etc. Todas las devoluciones de llamada que anula ( onCreate()
, onDestroy()
...) se envían al menos indirectamente desde ese bucle. Puedes ver eso, por ejemplo, en el rastro de la pila de una excepción. (Puede intentarlo, simplemente escriba int a = 1 / 0;
onCreate()
int a = 1 / 0;
en algún lugar de onCreate()
...)
Espero que esto tenga sentido. Perdón por no haber sido claro previamente.