【Android 消息机制】Handler

发布于:2025-09-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

- 参考资料

1. 基本原理

Handler机制是Android中基于单线消息队列模式的一套线程消息机制
Handler的设计是基于生产者和消费者模型的方式;
工作流程如下:

  • handler将消息发送到Looper的消息队列中(messageQueue)
  • messageQueue 将数据按照时间先后排好队,等待Looper.loop()按照先后顺序取出Message
  • Looper.loop()取出消息之后,调用消息的Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中.
  • handler 在 handleMessage(msg)方法中处理我们自己的逻辑。
    在这里插入图片描述

在这里插入图片描述

2. 核心对象

2.1 Handler

添加链接描述

2.1.1 常用方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1.2 小结

  • 核心方法
  • 主线程和子线程创建handler的区别
  • 基本工作原理(与其他三个组件的联动关系)
  • 内存泄漏问题
  • 数量问题
    在这里插入图片描述

2.2 Looper

添加链接描述

2.2.1 概述

在这里插入图片描述

2.2.2 源码分析

(1) prepare

在这里插入图片描述
Looper 采用静态变量 sThreadLocal:ThreadLocal 管理着与线程相关的 Looper 实例
当重复调用 Looper.prepare() 方法时,如果 sThreadLocal:ThreadLocal.get() 不为空,则会抛异常。
由此保证了一个线程 Thread 有且只有一个与之关联的 Looper 对象

(2) loop()
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    final MessageQueue queue = me.mQueue;

    // 无限循环
    for (;;) {
        Message msg = queue.next(); // 可能会阻塞
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // 分发消息前的钩子
        me.mLogging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback);

        // 分发消息给对应的 Handler
        msg.target.dispatchMessage(msg);

        // 分发消息后的钩子
        me.mLogging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

        // 回收消息对象
        msg.recycleUnchecked();
    }
}

在一个无限循环中,不断重复以下操作:
① 调用 MessageQueue.next() 获取下一个消息:该方法会阻塞;如果返回 null,则退出循环。
② 当有返回消息且不为空时,调用 Message.target.dispatchMessage() 方法分发消息。
③ 把分发后的消息对象 Message,回收到消息池中以便复用。
进入下一个循环。

(3) quit()/quitSafely()

在这里插入图片描述
退出消息循环,终止 loop()。Looper 被退出后,任何向队列发布消息的尝试都将失败。
quit():直接移除消息队列中所有消息,使得 MessageQueue.next() 因为没有消息而返回null,致使Looper.loop() 退出循环。
quitSafely():只会剔除执行时刻 when 晚于当前调用时刻的 Message 消息。这样可以保证 quitSafely() 调用的时刻,满足执行时间条件的 Message 会继续保留在队列中被执行并在所有可执行的消息都执行完毕之后才退出 loop() 的轮询。
由上可知,方法内部是通过调用消息队列 MessageQueue.quit() 方法来实现的。

(4) Looper.myLooper()

在这里插入图片描述
返回与当前线程关联的 Looper 对象。 如果调用线程未与 Looper 关联,则返回 null。

(5) 构造方法

quitAllowed表示该 Looper 是否允许退出(即是否允许调用 quit())

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

2.3 MessageQueue

添加链接描述

2.3.1 概述

在这里插入图片描述

2.3.2 源码分析

(1) enqueueMessage
    boolean enqueueMessage(Message msg, long when) {
        // target是handler对象,检查消息是否有线程处理
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // msg是否已经被使用
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            // 处理消息的线程是否已经退出,如果线程退出,则将消息回收掉,无需进队列
            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();
                return false;
            }

            // 标记消息已经被使用
            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 {
                // 将新消息插入到队列中,同步消息根据when来确定队列位置,如果是同步屏障,同步屏障消息target==null,且新消息是异步的,则needWake为true,则计划唤醒队列。
                // 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;
                    }
                    // 如果队头的一下个消息也是异步消息,且未到唤醒时间,则将needWake置为false,不唤醒队列
                    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;
    }

首先是对消息进行检验,消息能否被处理和消息是否正在被使用等。msg.target是指Handler对象。当处理消息的线程已经退出,则消息会被回收。
针对同步消息:将时间小的消息放在靠近队头的位置,时间长的消息靠近队尾
从队头开始,找到新消息在队列中合适的位置,然后将新消息插入到队列中,如果队列为null或新消息的等待时间小于队头消息的等待时间,则直接将新消息放入到队列的头部。如果队头消息的时间小于新消息,则新消息将从队头消息开始依次和队列中的消息进行比较,直到找到合适的位置。
这里特别说明下:同步消息和异步消息只有isAsynchronous()可以区分,即异步标志位区分,异步标志位是通过msg的setAsynchronous(boolean async)方法进行设置,异步和同步消息均是根据when来确定在队列中的位置。

(2) 读取消息的next方法
    Message next() {
        // 只有当线程退出时,mPtr为null
        // 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;
                }
                ...
        }
    }

}

在next方法中,当发现队列中有同步屏障时,此时会优先返回队列中的异步消息,从队列中获取异步消息,并将该异步消息从队列中移除,如果同步屏障不被移除,即使异步消息被处理完毕,同步消息也不会被处理,队列会进入阻塞状态。
如果队列中没有同步屏障,则从队列中获取同步消息,并将该同步消息从队列中移除。

2.4 Message

2.4.1 概述

  • 消息类型
  • 如何创建Message
    在这里插入图片描述

2.4.2 源码分析

(1)重要属性

比如target属性,对于屏障消息这里target就是null;
when属性,handler.postDelay方法底层利用了when属性

// 用户自定义,主要用于辨别Message的类型
public int what;
// 用于存储一些整型数据
public int arg1;
public int arg2;
// 可放入一个可序列化对象
public Object obj;
// Bundle数据
Bundle data;
// Message处理的时间。相对于1970.1.1而言的时间
// 对用户不可见
public long when;
// 处理这个Message的Handler
// 对用户不可见
Handler target;
// 当我们使用Handler的post方法时候就是把runnable对象封装成Message
// 对用户不可见
Runnable callback;
// MessageQueue是一个链表,next表示下一个
// 对用户不可见
Message next;

(2) obtain方法
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}
(3) recyler
public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}
void recycleUnchecked() {
    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) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

Message的作用就是承载消息,他的内部有很多的属性用于给用户赋值。同时Message本身也是一个链表结构,无论是在MessageQueue还是在Message内部的回收机制,都是使用这个结构来形成链表。同时官方建议不要直接初始化Message,而是通过Message.obtain()方法来获取一个Message循环利用一般来说我们不需要去调用recycle进行回收,在Looper中会自动把Message进行回收

3. 其他问题

3.1 HandlerThread

在这里插入图片描述

3.2 IdleHandler

在这里插入图片描述

3.3 主线程Handler的流程图

在这里插入图片描述

4. 思维导图

  • 概述
    通过网盘分享的文件:消息机制Handler.xmind
    链接: https://pan.baidu.com/s/1kEuvqI4LFb–Y3HBkXLnaQ 提取码: wce4 复制这段内容后打开百度网盘手机App,操作更方便哦

  • 源码思维导图

  • 在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到