一文搞懂Handler机制原理

article/2025/11/6 2:26:51

前言

Android提供了Handler来满足线程间的通信,开发中不管直接还是间接基本离不开Handler的使用,通常Handler被我们用来做子线程更新UI线程的工具,可以说只要有子线程与主线程通信的地方就会有Handler。
工欲善其事必先利其器,熟悉Handler机制可以帮助我们更好的处理工作中遇到的问题。

1. 使用示例

private Handler mHandler;
public void test() {new Thread(){@Overridepublic void run() {Looper.prepare();initHandler();Looper.loop();}}.start();
}
private void initHandler() {mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {// 处理消息}};
}// 通过Handler发送消息
mHandler.sendMessage(message);
mHandler.post(runnable);
...

2. Java源码分析

主要涉及到以下几个类:

  • Message (消息实体)
  • Handler (处理Message)
  • MessageQueue (维护Message的队列,插入和取出Message)
  • looper (循环从MessageQueue中取Message)

Handler机制的核心是发送与处理Message,那么我们先过一遍Message类。

2.1 Message实体类

public final class Message implements Parcelable {...// 处理Message的时间点public long when;// 持有处理Message的Handler对象/*package*/ Handler target;// 用来记录Message链表的下一个节点/*package*/ Message next;// Message复用池(也是链表格式)private static Message sPool;// Message复用池中当前Message个数private static int sPoolSize = 0;// Message复用池最大Message个数private static final int MAX_POOL_SIZE = 50;...// 从Message复用池中获取Message,如果没有缓存才创建新的Messagepublic static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // 清除in-use标志sPoolSize--;return m;}}return new Message();}void recycleUnchecked() {// 将消息标记为in-use,清空其他信息,插入Message复用池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++;}}}// 是否异步消息public boolean isAsynchronous() {return (flags & FLAG_ASYNCHRONOUS) != 0;}// 设置为同步或者异步消息public void setAsynchronous(boolean async) {if (async) {flags |= FLAG_ASYNCHRONOUS;} else {flags &= ~FLAG_ASYNCHRONOUS;}}...}

Message是一个封装消息的实体类,主要对简单的消息做一层包装,方便后续处理,里面几个比较重要的字段和方法如上,其中when是待处理消息的时间点,target是Handler对象的引用,sPool是消息复用池,因为Android交互是基于消息机制,如果不进行复用,频繁的处理消息就会不断的创建销毁Message对象,导致内存抖动,所以创建Message时,Google推荐使用Message.obtain()来获取Message对象。recycleUnchecked()是将当前Message清空并插入复用队列。

2.2 发送Message流程

主要是通过Handler将Message按照时间When字段插入MessageQueue中。

2.2.1 Handler类

我们先来看sendMessage(message)和post(runnable)。

public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);
}public final boolean post(@NonNull Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);
}public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}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);
}

可以看到,不管是sendMessage还是post都调用了sendMessageDelayed方法,而sendMessageDelayed里面调用了sendMessageAtTime并传入SystemClock.uptimeMillis() + delayMillis(当前时间+延迟时间)参数,也就是消息真正待处理的时间点,sendMessageAtTime又调用了enqueueMessage方法,通过方法名我们基本上就可以判断该方法是将消息插入队列,我们接着往下看。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}

在Handler的enqueueMessage方法中,调用了MessageQueue的enqueueMessage方法;注意这里有一个重点,Message的target指向了当前Handler对象,也就是说每个Message消息都会持有Handler对象的引用,这也就是为什么非静态内部类Handler可能会出现内存泄漏的原因。

2.2.2 MessageQueue类

将Message插入MessageQueue。

boolean enqueueMessage(Message msg, long when) {...// 同步锁,多线程同步的核心synchronized (this) {...// when = 当前时间 + 延迟时间msg.when = when;// 获取MessageQueue中保存的Message队列头Message p = mMessages;boolean needWake;// 如果Message队列为空或者新Message的when小于队列头// 将新Message插入到队列头部if (p == null || when == 0 || when < p.when) {msg.next = p;mMessages = msg;// 如果阻塞则唤醒事件needWake = mBlocked;} else {// 插入到队列中间。通常不必唤醒事件队列,除非// 队列的头部有屏障,并且消息是队列中最早的异步消息。needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;// 根据当前Message的when循环查找合适的位置插入,保证队列中// 的Message根据时间顺序排列。for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; prev.next = msg;}if (needWake) {nativeWake(mPtr);}}return true;
}

MessageQueue中持有一个链表形式的Message队列,enqueueMessage方法主要就是将待发送的Message根据时间顺序插入到消息队列中,并判断是否需要唤醒处理消息队列的线程,如果需要,通过调用native方法nativeWake()将该线程唤醒。

2.3 取出Message流程

主要是通过Looper循环从MessageQueue中拿出时间When字段与当前时间匹配的Message。

2.3.1 Looper类

Looper.prepare()主要是初始化Looper,并通过ThreadLocal能力保证此线程只有一个Looper,这里我就不细讲了。
我们看另外比较重要的Looper.loop()方法。

public static void loop() {...for (;;) {Message msg = queue.next(); // 可能阻塞if (msg == null) {// 没有消息表明消息队列调用了退出return;}...msg.target.dispatchMessage(msg);...msg.recycleUnchecked();}
}

loop方法的主要作用是循环从MessageQueue中获取Message,并通过Message.Target(也就是Handler)调用dispatchMessage。

我们看看MessageQueue的next方法。

2.3.2 MessageQueue类

Message next() {...int nextPollTimeoutMillis = 0;for (;;) {...nativePollOnce(ptr, nextPollTimeoutMillis);// 同步锁,保证多线程之间Message数据同步synchronized (this) {// 尝试检索下一条Message,找到就返回final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// 同步屏障,查找下一条异步消息do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// 还未到消息的执行时间,计算时间差nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 当前Message执行时间已到,返回此MessagemBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;msg.markInUse();return msg;}} else {// 没有更多MessagenextPollTimeoutMillis = -1;}...}...nextPollTimeoutMillis = 0;}
}

外部循环去检索Message,内部do while循环增加同步屏障检索异步Message,如果有异步Message则先处理异步,拿到Message之后判断时间when是否晚于当前时间,晚于当前时间的话计算时间差并保存到nextPollTimeMills,待下次循环开始然后调用native方法nativePollOnce()来进行阻塞,nativePollOnce()方法在native层使用到了Linux的epoll机制,并且native层也有一套Handler机制,这里先不做细致讲解,免得增加复杂度,总之内部在没有到处理消息的时间时会阻塞线程,释放CPU,直到拿到当前时间到达目标时间When的Message。

2.3.3 Handler类

接下来我们进入dispatchMessage看看做了啥。

public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}private static void handleCallback(Message message) {message.callback.run();}

可以看到,dispatchMessage中判断msg.callback不为空则调用callback的run方法,也就是我们通过Handler.post(runnable)等传入的Runnable的run方法,否则调用handleMessage。

3. 整体流程

流程图

4. 小结

  • Handler类主要处理收发消息,通过调用MessageQueue的enqueueMessage()方法将Handler发送的Message插入MessageQueue,并且内部有同步锁,保证多线程同步。
  • Looper主要通过loop()方法循从MessageQueue中取出Message,从MessageQueue中取出消息内部也有同步锁,保证多线程同步。
  • Handler消息机制,在Java层的MessageQueue中,调用了native方法实现线程的休眠和唤醒。native方法nativePollOnce()实现了消息循环的休眠,而nativeWake()方法则实现了消息循环的唤醒。

http://chatgpt.dhexx.cn/article/RMeBR4sl.shtml

相关文章

handler机制原理

一,什么是handler handler是消息机制的一个上层接口 更新UI的操作 耗时完毕后发送消息给主线程更新UI 耗时操作只能在子线程中执行,Android是线程不安全的你不能在子线程中更新UI 所以Android引入了handler机制 handler通过发送和处理message和Runnable对象来关联相对应线程的…

Android Handler消息机制原理最全解读(持续补充中)

本文主要详细去解读Android开发中最常使用的Handler&#xff0c;以及使用过程中遇到的各种各样的疑问。 Handler 在Android开发的过程中&#xff0c;我们常常会将耗时的一些操作放在子线程&#xff08;work thread&#xff09;中去执行&#xff0c;然后将执行的结果告诉UI线程…

Handler机制

1.为何引入Handler机制 Handler是线程间通讯的机制&#xff0c;Android中&#xff0c;网络访问、文件处理等耗时操作必须放到子线程中去执行&#xff0c;否则将会造成ANR异常。 ANR异常&#xff1a;Application Not Response 应用程序无响应 产生ANR异常的原因&#xff1a;在…

Handle消息机制解析

概述 Handler消息机制(由Handler/Looper/MessageQueue等构成)&#xff0c;Android有大量的消息驱动方法来进行交互&#xff0c;就像Android的四大组件(Activity、Service、Broadcast、ContentProvider)的启动过程交互&#xff0c;都离不开Handler的消息机制&#xff0c;所以An…

Handler机制(一)——Handler运行流程分析

1 概述 Handler机制是Android的异步消息处理机制&#xff0c;用于在线程间传递消息&#xff0c;主要涉及到四部分&#xff1a;Handler、Looper、Message和MessageQueue。其中Handler是消息的发送者和处理者&#xff1b;Message是消息主体&#xff1b;MessageQueue是消息队列&a…

reshape的作用

reshape就是矩阵的变换就是行和列相乘的数相等就可以相互变换

reshape函数

在opencv中&#xff0c;reshape函数比较有意思&#xff0c;它既可以改变矩阵的通道数&#xff0c;又可以对矩阵元素进行序列化&#xff0c;非常有用的一个函数。 函数原型&#xff1a; C: Mat Mat::reshape(int cn, int rows0) const 参数比较少&#xff0c;但设置的时候却要千…

Reshape的命令应用

import numpy as np tnp.arange(0,64).reshape(8,8) print(t) Reshape 的参考使用&#xff1a; (1条消息) Python的reshape的用法&#xff1a;reshape(1,-1)_冷月无声的博客-CSDN博客_reshape函数pythonhttps://blog.csdn.net/qq_29831163/article/details/90112000Reshape主…

matlab中reshape的用法,reshape2 函数 reshape 的用法

函数 reshape 的用法 请我在MATLAB编程中遇到了一个问题&#xff0c;函数reshape的用法我就是没有弄B reshape(A,m,n) 返回一个m*n的矩阵B&#xff0c; B中元素是按列从A中得到的。如果A中元素个数没有m*n个&#xff0c; 则会引发错误。 B reshape(A,m,n,p,...)和B reshape(…

Numpy之reshape()详解

Numpy中reshape的使用方法为:numpy.reshape(a, newshape, orderC) 参数详解&#xff1a;1.a: type:array_like(伪数组&#xff0c;可以看成是对数组的扩展&#xff0c;但是不影响原始数组。) 需要reshape的array2.newshape:新的数组 新形状应与原形状兼容。如果是整数&#xf…

利用Numpy库的方法reshape()对ndarray对象矩阵的形状进行调整

利用Numpy库的函数reshape()对ndarray对象矩阵的形状进行调整 调整矩阵或图像的形状是一个常用的操作。 在Numpy库中&#xff0c;可使用函数reshape()实现此操作。 其函数原型如下&#xff1a; dst numpy.reshape(a, newshape[, orderC])参数意义如下&#xff1a; a—需要调…

关于reshape

X.reshape(X.shape[0], -1).T和X.reshape(-1&#xff0c;X.shape[0]) 虽然矩阵形式仍然一致但矩阵元素排列完全不同 在降低测试集维度时注意&#xff0c;应使用X.reshape(X.shape[0], -1).T

matlab reshape 用法,函数 reshape 的用法

函数 reshape 的用法别问小编过得好不好不好你也帮助不了好也不是你的功劳。 请小编在MATLAB编程中遇到了一个问题,函数reshape的用法小编就是没有弄B = reshape(A,m,n) 返回一个m*n的矩阵B, B中元素是按列从A中得到的。如果A中元素个数没有m*n个, 则会引发错误。 你知道失望…

【晕头晕脑的Python】Python中Reshape函数解析

Reshape函数解析 Reshape()作用&#xff1a;Reshape()实例说明&#xff1a;一维reshape() 为 二维二维数组 reshape 切片&#xff0c;逆置三维Reshape情况 Reshape()作用&#xff1a; Reshape&#xff08;&#xff09;&#xff0c;函数的作用就是将数据的按照既定的维度进行整…

python中reshape的用法

python中reshape的用法 reshape函数的使用&#xff1a; #reshape&#xff08;&#xff09;是数组对象中的方法&#xff0c;用于改变数组的形状 arr [1,2,3,4,5,6,7,8,9] import numpy as np arrnp.array(arr) #一维 #变成一个3 * 3的二维矩阵&#xff1a; #方法一 arr.resha…

Python的reshape的用法

numpy中reshape函数的三种常见相关用法 reshape(1,-1)转化成1行&#xff1a; reshape(2,-1)转换成两行&#xff1a; reshape(-1,1)转换成1列&#xff1a; reshape(-1,2)转化成两列 numpy中reshape函数的三种常见相关用法 numpy.arange(n).reshape(a, b) 依次生成n个自然…

python中reshape函数用法详解

python中reshape函数用法详解 reshape函数 reshape函数是Numpy库中的一个函数&#xff0c;可以用于改变一个数组的形状&#xff0c;例如将一个二维数组转换成一个三维数组。 import numpy as np # 创建一个二维数组&#xff0c;形状为(4, 6) a np.array([[1, 2, 3, 4, 5, 6]…

ORA-12162 :TNS 指定的网络服务名不正确

原因&#xff1a;这台服务器有多个库 在环境变量文件/home/oracle/.bash_profile中也没有export ORACLE_SIDxxx 解决方法&#xff1a; 登录前先export ORACLE_SIDxxx 再确保查看一下echo $ORACLE_SID 再登录sqlplus / as sysdba

ORA-12162错误解决

新来的一个小伙儿&#xff0c;想学习Oracle我给了他文档&#xff0c;自己研究着&#xff0c;下午时段叫我过去&#xff0c;发现其在装oracle后&#xff0c;进行测试时报ORA-12162错误&#xff0c;正好本鸟之前也遇到过&#xff0c;这里面贴出了大家如有遇到不要惊慌。 报错图片…

ORA-12162: TNS:net service name is incorrectly specified

概述 因未设置系统环境变量ORACLE_SID导致ORA-12162错误 分析原因 首先登录数据库主机执行 oerr ora 12162 我们首先查看看下 tnsnames.ora文件 执行tnsping CC命令 检查下是否可以tnsping通&#xff0c;核对IP和端口以及实例名 数据库服务器端使用TNSNAMES.ORA中记录的…