Android Handler 机制详解

1,250 阅读6分钟

31983.jpg

介绍

Handler是Android的消息机制,可以用于App内部通信,比如线程间通信。这套机制的实现基于生产者和消费者模型,下面我会回答什么是生产者和消费者模型。Handler是如何将消息放入消息池,以及消息被谁什么时候取出这些问题进行讲解

一、思维导图

Handler原理分析.jpg 这张图基本囊括了Handler的所有知识点,可作为学习Handler的思维导图

二、Handler 设计思想

Handler的设计是基于生产者和消费者模型的方式,在实际应用中可以完成线程之间的通信。在一个生产者-消费者模式中,通常会有三个角色:生产者、消费者和消息队列。它们的工作流程如下图所示:

image.png

  • 生产者: 负责生产消息,塞到共享的消息队列中可以有多个

  • 消费者: 负责从共享消息队列中取出消息,执行消息对应的任务

  • 消息队列: 负责存取消息

这三个角色中,因为生产者和消费者要从同一个消息队列取放消息,所以消息队列的数量要求是唯一的,生产者和消费者的数量可以任意

三、工作原理

Handler工作原理 (3).jpg

Handler 可以发送发处理与线程MessageQueue关联的Message。当App启动时会执行ActivityThread#main方法,创建一个Lopper,他会绑定到创建它的线程。从那时起,它将消息从队列中取并分发给处理者执行它们。如果在子线程的话,要自己创建Looper,并调用loop轮询消息

3.1 类图说明

Handler:消息的生产者,负责发生消息也是消息的处理者

Looper:消息的消费者,从消息队列里面取消息,交给Handler处理

Message:消息体是一个单链表结构,生产过来的消息按时间维度以单链表的形式串起来

MessageQueue:用来管理Message,负责消息的插入和取出

4、流程图

Handler时序图.png

这张图以主线程创建Looper为例,App启动之后执行ActivityThread#main方法,会去创建消息消费者Looper,Looper不断的轮询MessaqeQueue是否有新消息,没有消息的话无限休眠等待被唤醒,由于epoll机制会释放当前的cpu资源不会阻塞住主线程。直到MessageQueue有新消息加入之后,唤醒轮询。然后Looper将消息取出,并交给消息处理者

4.1 核心代码

消费消息

// 1. ActivityThread#Main
// 初始化Looper
public static void main(String[] args) {
    Looper.prepareMainLooper();
    Looper.loop();
}

// 2. Looper#loop 消费消息
// 从MessageQueue中取消息然后进行下发
public static void loop() {
    final Looper me = myLooper();

    final MessageQueue queue = me.mQueue;

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

        msg.recycleUnchecked();
    }
}

// 3. MessageQueue#next
// 取出位于头部的消息如果没有的话会进入block
// 取出消息的类型根据是否开启了同步屏障为条件
// 开启取异步消息,为开启取同步消息
// 最后执行空闲Handler即idlerHandler
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new MessageQueue.IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final MessageQueue.IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

生产消息

// 4.Handler#enqueueMessage
// 将消息传递给MessageQueue
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                               long uptimeMillis) {
    return queue.enqueueMessage(msg, uptimeMillis);
}

// 5.MessageQueue#enqueueMessage 插入消息
// MessageQueue是按照Message触发时间的先后顺序排列的,
// 队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,
// 会从队列头开始遍历,直到找到消息应该插入的合适位置,
// 以保证所有消息的时间顺序。
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 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;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

五、常见问题

5.1 什么是同步屏障

MessageQueue 中的postSyncBarrier()方法用来发送同步屏障,此方法被标记为hide。它的作用是让异步消息优先得到执行。当方法被调用时会往MessageQueue中添加一条没有target的Message。此时Looper将优先消费异步消息,同步消息被挂起无限等待。直到调用removeSyncBarrier(token)移除同步消息,恢复所有消息的正常处理。

5.2 什么是IdleHandler

Handler 提供了一种当消息队列出现空闲的时候,允许我们执行任务的一种机制。触发时机在MessageQueue取不到消息的时候,或者处理的消息是一个延迟消息的时候。

5.3 为什么会导致内存泄漏

内存泄漏的本质是生命周期的异常,没有及时释放,或者长期得不到释放。使用Handler导致的内存泄漏,是因为退出页面时没有切断GC Root 抵达 Activity 的引用链。 比如以下场景:

  1. 退出的时候仍有 Thread 在处理中,其引用着 Handler
  2. Thread 结束了,但 Message 尚在队列中排队处理或正在处理中,间接持有 Handler

解决方案

  1. 使用 Handler 时采用静态内部类 + 弱引用,避免其强引用持有 Activity 的实例
  2. 在 Activity 结束的时候,及时地清空 Message终止 Thread 或退出 Looper

(以上的问题为了抛砖引玉,细节的话可以看看我的参考资料)

参考资料

[1]  Android Handler消息机制解读
[2]  Android组件系列:Handler机制详解
[3]  Android消息机制1-Handler Java层
[4]  移动架构 (二) Android 中 Handler 架构分析
[5]  Android同步屏障机制
[6]  Handler之postSyncBarrier消息屏障
[7]  Android图形渲染之Choreographer原理
[8]  Handler的初级、中级、高级问法
[9]  IdleHandler 的原理分析和妙用