当前位置: 首页 > ai >正文

【Android 消息机制】Handler

文章目录

  • 1. 基本原理
  • 2. 核心对象
    • 2.1 Handler
      • 2.1.1 常用方法
      • 2.1.2 小结
    • 2.2 Looper
      • 2.2.1 概述
      • 2.2.2 源码分析
        • (1) prepare
        • (2) loop()
        • (3) quit()/quitSafely()
        • (4) Looper.myLooper()
        • (5) 构造方法
    • 2.3 MessageQueue
      • 2.3.1 概述
      • 2.3.2 源码分析
        • (1) enqueueMessage
        • (2) 读取消息的next方法
    • 2.4 Message
      • 2.4.1 概述
      • 2.4.2 源码分析
        • (1)重要属性
        • (2) obtain方法
        • (3) recyler
  • 3. 其他问题
    • 3.1 HandlerThread
    • 3.2 IdleHandler
    • 3.3 主线程Handler的流程图
  • 4. 思维导图

- 参考资料

  • 添加链接描述
  • 添加链接描述
  • 添加链接描述

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);// 分发消息给对应的 Handlermsg.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.nextprev.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 iterationint 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 flagsPoolSize--;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,操作更方便哦

  • 源码思维导图

  • 在这里插入图片描述

http://www.xdnf.cn/news/20216.html

相关文章:

  • PDF教程|如何把想要的网页保存下来?
  • docker 推送仓库(含搭建、代理等)
  • 服务器线程高占用定位方法
  • 使用 Shell 脚本监控服务器 IOWait 并发送邮件告警
  • Python带状态生成器完全指南:从基础到高并发系统设计
  • C#实现导入CSV数据到List<T>的完整教程
  • 【基础-单选】用哪一种装饰器修饰的struct表示该结构体具有组件化能力?
  • Playwright携手MCP:AI智能体实现自主化UI回归测试
  • 第26节:GPU加速计算与Compute Shader探索
  • Homebrew执行brew install出现错误(homebrew-bottles)
  • Go语言后端开发面试实战:谢飞机的“硬核”面试之旅
  • CodeBuddy 辅助重构:去掉 800 行 if-else 的状态机改造
  • Eclipse下的一些快捷键备忘录
  • LangChain实战(十九):集成OpenAI Functions打造强大Agent
  • Day37 MQTT协议 多客户端服务器模型
  • 手写MyBatis第53弹: @Intercepts与@Signature注解的工作原理
  • 工业洗地机和商用洗地机的区别是什么?
  • 【基础-单选】关于bundleName,下列说法正确的是?
  • 波特率vs比特率
  • rh134第三章复习总结
  • 贪心算法应用:保险理赔调度问题详解
  • Java中的死锁
  • 使用 MongoDB.Driver 在 C# .NETCore 中实现 Mongo DB 过滤器
  • [数据结构] ArrayList(顺序表)与LinkedList(链表)
  • 万代《宝可梦》主题新品扭蛋公开!史上最大尺寸
  • 机器人控制器开发(传感器层——奥比大白相机适配)
  • 【FastDDS】Layer Transport ( 05-Shared Memory Transport)
  • 天气预报云服务器部署实战
  • 在Java AI项目中实现Function Call功能
  • 计算机毕设大数据方向:基于Spark+Hadoop的餐饮外卖平台数据分析系统【源码+文档+调试】