Un artículo sobre cómo comprender el mecanismo del controlador de Android

Breves instrucciones para usar Handler:

El controlador a menudo usa la comunicación por subprocesos en Android. Un uso común es que el subproceso envía mensajes al subproceso principal y luego actualiza la interfaz de usuario. Pero, de hecho, Handler también se puede utilizar para comunicarse entre dos subprocesos cualesquiera.

1. El hilo secundario envía un mensaje al hilo principal:

private Handler mHandler = new Handler(){
    
    
        //重写方法接收消息
        @Override
        public void handleMessage(Message msg) {
    
    
            super.handleMessage(msg);
            switch (msg.what) {
    
    
                //判断消息类型
                case MESSAGE_WHAT:
                    Log.d(TAG, "主线程接收到的子线程的数据: " + ((String) msg.obj) + "   :此时我可以更新UI");
                    break;
            }
        }
    };
    private void sendMessageToMainThreadByWorkThread() {
    
    
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                Message message = mHandler.obtainMessage(MESSAGE_WHAT);//获取消息
                message.obj = "我是来自工作线程的数据";
                mHandler.sendMessage(message);//发送消息
            }
        }.start();
    }
    /*
    * 通常我们在主线程中创建一个Handler,
    * 然后重写该Handler的handlerMessage方法,可以看到该方法传入了一个参数Message,
    * 该参数就是我们从其他线程传递过来的信息。
    *
    * 我们在来看下子线程中如何传递的信息,子线程通过Handler的obtainMessage()方法获取到一个Message实例,
    * 看Message的几个属性:
    * Message.what------------------>用来标识信息的int值,通过该值主线程能判断出来自不同地方的信息来源
    * Message.arg1/Message.arg2----->Message初始定义的用来传递int类型值的两个变量
    * Message.obj------------------->用来传递任何实例化对象
    * 最后通过sendMessage将Message发送出去。
    *
    * Handler所在的线程通过handlerMessage方法就能收到具体的信息了,如何判断信息的类型呢?-->当然是通过what值咯,通过what我们就知道接收到了哪种消息,以便执行对应的操作
    * 
    */

子线程与子线程的通信: 既然主线程跟子线程的通信是因为子线程持有了主线程handler的引用来发送消息给主线程,那么同理子线程A也可以通过持有子线程B的handler引用来发送消息给B线程,最后达到通信的目的

como sigue:

    private Handler handler;
    private void handlerDemoByTwoWorkThread() {
    
    
        Thread thread_A = new Thread() {
    
    
            @Override
            public void run() {
    
    
                Looper.prepare(); //获取Looper对象
                //在子线程中创建Handler
                handler = new Handler() {
    
    
                    @Override
                    public void handleMessage(Message msg) {
    
    
                        Log.d(TAG, "thread_A 接收到消息: " + ((String) msg.obj));
                        Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show();
                    }
                };
                Looper.loop(); //轮询消息
            }
        };
        Thread thread_B = new Thread() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                Message message = handler.obtainMessage();//获取消息
                message.obj = "Hi A";
                handler.sendMessage(message);//线程B发送消息给线程A
            }
        };
        thread_A.setName(" Thread_A");
        thread_A.start();
        thread_B.setName("Thread_B");
        thread_B.start();
        /*
        * 搞定,此处创建了两个Thread,thread_A和thread_B两个线程
        * 跟之前的代码没太大区别thread_A创建了Handler,thread_B通过Handler发送了消息。
        * 只不过此处我们只发送一个消息,所以没有使用what来进行标记
        * 值得注意的是:这里多了Looper.prepare() 以及Looper.loop();
        */
    }

PD: ¿Qué pasará si usas Looper.prepare() y Looper.loop()?

La respuesta es: se informará el siguiente error—>java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
源码如下:

         //在public Handler(@Nullable Callback callback, boolean async)构造方法中
        mLooper = Looper.myLooper();//获取线程的Looper
        if (mLooper == null) {
    
    
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }

Por tanto, el motivo del error es que nuestro Handler no tiene un objeto Looper. (Debido a que el método Looper.prepare genera un objeto Looper, el método Looper.loop() sondea y distribuye mensajes continuamente)

那么在上面主线程中为什么不需要Looper.prepare()和Looper.loop()呢?

De hecho, se debe al procesamiento realizado en el método ActivityThread.main (inicialización de la aplicación). El código fuente es el siguiente:

        Looper.prepareMainLooper(); //获取了主线程的Looper
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
    
    
            sMainThreadHandler = thread.getHandler();
        }
        if (false) {
    
    
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }  
        Looper.loop(); //不断轮询消息
/*
 真相只有一个,是的在创建主线程的时候Android已经帮我们调用了Looper.prepareMainLooper()
 和Looper.loop()方法,所以我们在主线程能直接创建Handler使用。
*/

Resumen: el subproceso secundario debe llamar a Looper.prepare() antes de crear una instancia de Handler. Después de crear la instancia de Handler, debe llamar a Looper.loop() para sondear y distribuir mensajes. El hilo principal no es necesario porque el sistema lo ha procesado por adelantado.

La conexión entre clases importantes Handler, Looper, MessageQueue y Message

Handler: Responsable de enviar y procesar mensajes;

Mensaje: objeto de mensaje, similar a un nodo de una lista vinculada;

MessageQueue: cola de mensajes, una estructura de datos utilizada para almacenar objetos de mensajes;

Looper: el controlador de la cola de mensajes (el objeto de mensaje utilizado para sondear la cola de mensajes);

Handler发送消息时调用MessageQueue的enqueueMessage插入一条信息到MessageQueue,Looper不断轮询调用MeaasgaQueue的next方法 如果发现message就调用handler的dispatchMessage,dispatchMessage被成功调用,接着调用handlerMessage()。

¿Cuántos Handlers, cuántos Loopers y cuántos objetos MessageQueue puede tener un hilo?

Analiza el código fuente:

Comencemos con Looper.parpare():

    private static void prepare(boolean quitAllowed) {
    
    
        if (sThreadLocal.get() != null) {
    
    
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    /**
     * 值得注意的是 
     static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
      Looper是用ThreadLocal来存储和获取的,这样确保了一个线程只有一个Looper
      关于ThreadLocal不清楚可以自行查资料
    */

Echemos un vistazo a lo que se hace en Looper.loop():

    public static void loop() {
    
    
        final Looper me = myLooper();//获取当前线程的Looper对象
        if (me == null) {
    
    
           //Looper为null将报错,意思是提示先要获取Looper对象
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//在Looper中,final MessageQueue mQueue; <-- final修饰,说明一个Looper对应一个MessageQueue
        /*
         * 只看重点-->中间省略一大坨代码
        */
        for(;;){
    
    
            Message msg = queue.next(); // 获取消息,无消息时会阻塞进入等待状态,但可以操作UI,有消息则立马获得消息进行下面操作
            if (msg == null) {
    
    
                // No message indicates that the message queue is quitting. <--注释原文
                //--没有消息指示消息队列正在退出-- 主线程的话 说明程序退出了
                return;
            }
            /*
             *中间省略一大坨代码 
            */
            msg.target.dispatchMessage(msg); //分发消息  --target是我们的handler对象,后面会提到-- 
        }
    }

En este punto, básicamente podemos analizar que solo hay un MessageQueue en un hilo.

Echemos un vistazo a lo que hace el sendMessage(Message msg) del Handler:

/*
 * sendMessage内部其实调用的是sendMessageAtTime,最终调用enqueueMessage进行消息的发送
*/    
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    
    
        MessageQueue queue = mQueue;
        if (queue == null) {
    
    
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

Así que echemos un vistazo a lo que hace el enqueueMessage de Handler:

/*
 * 分析可知在Handler的enqueueMessage方法中,把handler对象赋值给Message的target
 * 最后调用MessageQueue中的enqueueMessage来发送消息
*/
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    
    
        msg.target = this;  //这里的target就是我们的handler对象了(this),呼应上述的loop()方法
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        if (mAsynchronous) {
    
    
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); //进入MessageQueue的enqueueMessage方法
    }

Después de analizar este punto, podemos inferir que puede haber varios objetos Handler en un hilo.

Ingrese el método enqueueMessage de MessageQueue y eche un vistazo:

    /*
     * 主要操作为把要分发的消息插入到合适的位置(根据要发送的时间大小来判断)
    */
    boolean enqueueMessage(Message msg, long when) {
    
    
           /*
            *此处省略了一大坨源码
           */
            Message p = mMessages; //获取消息队列存放的第一个消息(消息头节点)
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
    
    
                //此分支说明消息队列中没有消息,此msg为第一个消息 
                // New head, wake up the event queue if blocked.  
                msg.next = p;  
                mMessages = msg; 
                needWake = mBlocked; //唤醒当前队列,如果队列处于阻塞状态的话
            } else {
    
    
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous(); 
                Message prev;
                //将消息插入到消息队列尾部(即Message链表的尾部)
                for (;;) {
    
    
                    prev = p; 
                    p = p.next;
                    if (p == null || when < p.when) {
    
    
                        //退出条件是 当找到了 第一个比msg发送时间要晚的,或者到了消息队列尾
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
    
    
                        needWake = false; 
                    }
                }
                msg.next = p; // invariant: p == prev.next 
                prev.next = msg;  //最后一个消息置为msg(或者找到了第一个比msg要晚发送的消息,则把msg插入在它前面)
            }
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
    
    
                nativeWake(mPtr); //唤醒消息队列
            }
        }
        return true;
    }

Entonces ahora podemos concluir que solo hay un Looper y un MessageQueue en un hilo, pero puede haber múltiples Handlers.

Finalmente, echemos un vistazo a lo que hace DispatchMessage al distribuir mensajes:

/*
 *  在Looper.loop()中 msg.target.dispatchMessage(msg) 开始执行该方法; 
 * target就是我们的Handler对象了(上述分析),因此进入Handler的dispatchMessgae查看源码
*/
public void dispatchMessage(@NonNull Message msg) {
    
    
        if (msg.callback != null) {
    
    
            //mCallback 其实是通过handler.post(Runnable callback)传进来的callback对象
            handleCallback(msg);  //这是执行了对应callback对象的run方法
        } else {
    
    
            if (mCallback != null) {
    
    
              //mCallback(Handler.Callback,需要重写handleMessage方法)
              //可以通过Handler的构造方法传进来
                if (mCallback.handleMessage(msg)) {
    
    
                    return;
                }
            }
            handleMessage(msg);  //调用Handler对象重写的HandleMessage方法处理消息
        }
    }

最后再来总结一下handler机制的工作流程:Handler发送消息时调用MessageQueue的enqueueMessage插入一条信息到MessageQueue,Looper不断轮询调用MeaasgaQueue的next方法 如果发现message就调用handler的dispatchMessage,dispatchMessage被成功调用,接着调用handlerMessage()。

Notas sobre el mecanismo del controlador:

1. Cosas a tener en cuenta al usar Handler: pérdidas de memoria:

原因:

1. Las clases internas no estáticas y las clases internas anónimas en Java contendrán implícitamente referencias externas a la clase actual.

2. Inicializamos un Controlador en la Actividad usando una clase interna no estática, y este Controlador contendrá una referencia a la Actividad actual.

3. Si queremos que un objeto sea reciclado, la premisa es que ningún otro objeto haga referencia a él, por lo que cuando nuestra página de Actividad está cerrada, hay una relación de referencia: "Mensaje sin procesar/en proceso -> Instancia de controlador -> Externo Clase (Actividad)", si hay mensajes sin procesar en la cola de mensajes del Controlador/el mensaje se está procesando, la Actividad no se reciclará, lo que provocará una pérdida de memoria.

解决方案:

1. Establezca la subclase de Handler en una clase interna estática y use WeakReference para contener la instancia de Actividad.
private Handler mHandler = new BetterHandler(this);
static class BetterHandler extends Handler {
    
    
    private WeakReference<Activity> mActivity;
    BetterHandler(Activity activity) {
    
    
        mActivity = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
    
    
        super.handleMessage(msg);
        Activity activity = mActivity.get();
        if (activity != null) {
    
    
            //TODO:
        }
    }
}
2. Cuando la clase externa finalice su ciclo de vida, borre la cola de mensajes dentro del controlador.
@Override
protected void onDestroy() {
    
    
    handler.removeCallbacksAndMessages(null);//传入null就好
    super.onDestroy();
}

2. ¿Qué estructura de datos es MessageQueue?

MessageQueue内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。这点和传统的队列有点不一样,主要区别在于Android的这个队列中的消息是按照时间先后顺序来存储的,时间较早的消息,越靠近队头。 当然,我们也可以理解成,它是先进先出的,只是这里的先依据的不是谁先入队,而是消息待发送的时间

3. La diferencia entre Looper.quit/quitSafely

Cuando lo llamamos Looper的quit方法, en realidad se ejecuta MessageQueue中的removeAllMessagesLocked方法. La función de este método es borrar todos los mensajes en el grupo de mensajes MessageQueue, ya sean mensajes retrasados (延迟消息是指通过 sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)​​o no retrasados. Cuando 调用Looper的quitSafely方法realmente lo ejecutamos MessageQueue中的removeAllFutureMessagesLocked 方法, como puede ver por el nombre, este método solo se cerrará 清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理de forma segura. En comparación con el método de salida, la seguridad es que todos los mensajes no retrasados ​​​​se enviarán antes de borrar el mensaje.

Código fuente:

    void quit(boolean safe) {
    
    
        synchronized (this) {
    
    
            if (mQuitting) {
    
    
                return;
            }
            mQuitting = true;
            //核心调用
            if (safe) {
    
    
                removeAllFutureMessagesLocked(); 
            } else {
    
    
                removeAllMessagesLocked();
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

4. Resuma cómo se realiza la comunicación de subprocesos a través de Handler

当在A线程中创建handler的时候,同时创建了MessageQueue与Looper,Looper在A线程中调用loop进入一个无限的for循环从MessageQueue中取消息,当B线程调用handler发送一个message的时候,会通过 msg.target.dispatchMessage(msg);将message插入到handler对应的MessageQueue中,Looper发现有message 插入到MessageQueue中,便取出message执行相应的逻辑,因为Looper.loop()是在A线程中启动的,所以则回到了A线程,达到了从B线程切换到A线程进行通信的目的。

5. ¿Cuáles son las formas y diferencias en la creación de objetos Mensaje?

1.Message msg = new Message(); cree un nuevo objeto cada vez que necesite un objeto de mensaje, y cada vez que tenga que ir a la memoria del montón para abrir espacio de almacenamiento de objetos

2.Message msg = Message.obtain();obtainMessage puede evitar objetos de creación de mensajes duplicados. Primero determina si el grupo de mensajes está vacío. Si no está vacío, toma el mensaje del encabezado del grupo de mensajes y luego apunta el encabezado al siguiente. Si el grupo de mensajes está vacío, significa que no se ha ingresado ningún mensaje y luego se creará un nuevo objeto Mensaje. El grupo de mensajes se implementa utilizando la estructura de lista vinculada de mensajes y el valor máximo predeterminado del grupo de mensajes es 50. Después de que el controlador distribuya y consuma el mensaje en el bucle, el mensaje se reciclará y los datos internos del mensaje se borrarán y agregarán al encabezado de la lista de mensajes.

3.Message msg = handler.obtainMessage();El método get() también se llama internamente

Código fuente relacionado:

Mensaje.obtener:

  public static Message obtain() {
    
    
        //先从消息池中获取消息
        synchronized (sPoolSync) {
    
    
            if (sPool != null) {
    
    
                Message m = sPool; //获取消息池头消息
                sPool = m.next;
                m.next = null;
                m.flags = 0; 
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

Controlador.obtainMensaje:

    public final Message obtainMessage()
    {
    
    
        return Message.obtain(this);
    }

Código fuente relacionado sobre mensajes de reciclaje del grupo de mensajes:

/*
* MessageQueue中:
*/
boolean enqueueMessage(Message msg, long when) {
    
    
        /*
        * 此处省略一坨代码
        */  
        synchronized (this) {
    
    
           //在Message清空消息队列的Message等时会调用   MessageQueue的quit方法中将-->mQuitting = true;
            if (mQuitting) {
    
    
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle(); //关键在这个函数里去回收消息对象,内部调用recycleUnchecked()
                return false;
            }
           /*
            * 此处省略一坨代码
           */   
        }
        return true;
    }
/*
 * Message类中查看,
 * 1. 当MessageQueue的quit方法执行了 removeAllFutureMessagesLocked()或者removeAllMessagesLocked()时都会调用  (Message)p.recycleUnchecked()
   2. 在Looper的loop中,消息分发完也会调用 msg.recycleUnchecked(); 
*/
    void recycleUnchecked() {
    
    
        /*
        * recycleUnchecked()方法中将what、arg1、arg2、object等都重置了值,
        如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的              Message对象(即将Message放到了sPool缓存池的头部)
        */
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
        synchronized (sPoolSync) {
    
    
            //private static final int MAX_POOL_SIZE = 50;  
            if (sPoolSize < MAX_POOL_SIZE) {
    
    
                next = sPool;
                sPool = this; //消息池头指向回收的消息
                sPoolSize++;
            }
        }
    }

Esperando actualizaciones…

Supongo que te gusta

Origin blog.csdn.net/XJ200012/article/details/130539847
Recomendado
Clasificación