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

Android学习总结之handler源码级

一、核心类关系与线程绑定(ThreadLocal 的核心作用)

1. Looper 与 ThreadLocal 的绑定

每个线程的 Looper 实例通过 ThreadLocal<Looper> sThreadLocal 存储,确保线程隔离:

public final class Looper {// 线程本地存储,每个线程独有一个 Looperstatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();// 主线程 Looper(ActivityThread 中创建)static Looper sMainLooper; final MessageQueue mQueue; // 关联的消息队列final Thread mThread; // 绑定的线程private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread(); // 记录当前线程}// 线程首次调用,创建 Looper 并存储到 ThreadLocalpublic static void prepare() {prepare(false); // quitAllowed 默认 false(主线程不允许退出)}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)); // 存入当前线程的 Looper}// 获取当前线程的 Looperpublic static @Nullable Looper myLooper() {return sThreadLocal.get();}
}
  • 主线程:Android 框架在 ActivityThread.main() 中自动调用 Looper.prepareMainLooper() 和 Looper.loop(),无需手动处理。
  • 子线程:必须手动调用 Looper.prepare()(创建 Looper 和 MessageQueue)和 Looper.loop()(启动消息循环),否则 Handler 无可用 Looper 会报错。
2. Handler 的构造与 Looper 关联

Handler 实例必须与一个 Looper 绑定,默认使用当前线程的 Looper(通过 Looper.myLooper() 获取):

public class Handler {final Looper mLooper; // 关联的 Looperfinal MessageQueue mQueue; // Looper 的消息队列final Callback mCallback; // 消息回调public Handler() {this(null, false);}public Handler(@Nullable Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: "+ klass.getCanonicalName());}}mLooper = Looper.myLooper(); // 获取当前线程的 Looper(必须已 prepare)if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue; // 关联消息队列mCallback = callback;mAsynchronous = async;}
}
  • 若子线程未调用 Looper.prepare(),创建 Handler 时会抛出 RuntimeException,这就是子线程必须先准备 Looper 的原因。

二、消息发送:从 Handler 到 MessageQueue 的入队

1. Message 的创建与重用(消息池机制)

Message 优先从消息池获取,避免频繁 GC:

public final class Message implements Parcelable {// 消息池头节点(静态,所有线程共享)private static Message sPool;// 消息池大小(最大 50 个)private static int sPoolSize = 0;// 下一个可用消息(形成单链表)@UnsupportedAppUsageMessage next;// 从消息池获取消息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(); // 池空时新建}// 回收消息到池中(处理完后调用)void recycleUnchecked() {if (isInUse()) { // 确保未被使用if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycle();}private void recycle() {if (mRecycled) { // 已回收过则不再处理return;}mRecycled = true;if (sPoolSize < MAX_POOL_SIZE) { // 最大 50 个next = sPool;sPool = this;sPoolSize++;}}
}

  • 优势:减少对象创建开销,提升性能,尤其适合高频发送消息的场景。
2. MessageQueue 的入队逻辑(enqueueMessage)

消息按 msg.when(执行时间)排序插入,形成一个 非严格 FIFO 的有序单链表

boolean enqueueMessage(Message msg, long when) {msg.target = this; // target 指向发送消息的 Handlermsg.workSourceUid = ThreadLocalWorkSource.getUid();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) { // 新消息时间更早,插入头部msg.next = p;mMessages = msg;needWake = mBlocked; // 当前队列是否阻塞,决定是否唤醒} else { // 找到合适位置插入(按 when 升序)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;prev.next = msg;}// 若队列阻塞且需要唤醒(如 Looper.loop() 在等待),通过 native 方法唤醒if (needWake) {nativeWake(mPtr);}}return true;
}

  • 关键点
    • synchronized (this) 确保多线程安全,不同线程的 Handler 发送消息到同一 MessageQueue 时不会冲突。
    • 消息按 when 排序,而非严格的 FIFO,支持延迟消息(如 postDelayed)。
    • 异步消息(msg.setAsynchronous(true))可打断同步消息的等待,优先处理。

三、消息循环:Looper.loop () 的无限循环

1. loop () 方法核心逻辑
public static void loop() {final Looper me = myLooper(); // 获取当前线程的 Looperif (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue; // 关联的消息队列// 确保主线程 Looper 不会被 GC 回收(Binder 机制相关)Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) { // 无限循环,直到队列退出Message msg = queue.next(); // 取出消息(可能阻塞)if (msg == null) { // 队列退出(msg.next == null)return; // 退出循环}// 处理消息:通过 msg.target(Handler)分发msg.target.dispatchMessage(msg);// 回收消息到池中(非必须,系统自动处理)msg.recycleUnchecked();}
}
2. MessageQueue.next () 的阻塞与唤醒

next() 是消息循环的核心,通过 native 层实现阻塞等待:

Message next() {final long ptr = mPtr; // 指向 native 层的 MessageQueue 实例int pendingIdleHandlerCount = -1; // 首次调用时初始化为 -1int nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// native 层阻塞等待消息,直到有消息或被唤醒nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// 检查是否有消息待处理final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.when > now) { // 消息未到执行时间,计算延迟nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else { // 有可执行的消息,取出头节点msg = mMessages;mMessages = msg.next;msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);nextPollTimeoutMillis = -1; // 下次立即 poll}if (msg != null) { // 有消息,返回给 Looperreturn msg;}// 无消息,检查是否退出mQuitting = true;return null;}}
}
  • 阻塞原理:通过 nativePollOnce 进入内核等待,当消息入队时(enqueueMessage 中调用 nativeWake)被唤醒。
  • 退出条件:调用 Looper.quit() 或 Looper.quitSafely() 时,mQuitting 设为 true,next() 返回 null,loop() 终止。

扩展追问

消息队列阻塞之后又来了一个消息,它怎么知道这个消息来了呢?
一、阻塞的本质:基于 Linux 内核的 “事件等待” 机制

Android 的消息队列(MessageQueue)由 Looper 管理,当 Looper.loop() 发现队列中没有立即需要处理的消息时,会进入 “阻塞等待” 状态,但这种阻塞并非 “死循环”,而是基于 Linux 内核的 高效事件等待机制

  1. nativePollOnce 的底层实现

    • Looper 在 Java 层调用 nativePollOnce 方法(对应底层 C++ 代码),该方法会创建一个 管道文件描述符(Pipe FD),包含两个端点:读端(readFD)和写端(writeFD)。
    • 通过 Linux 的 epoll 或 poll 机制,将读端 readFD 注册到内核的事件监听中,然后让当前线程进入 休眠状态(不占用 CPU),等待内核通知 “有事件发生”。
  2. 为什么需要管道?

    • 管道是 Linux 中一种进程间通信机制,此处用于 线程内的事件通知:当消息队列需要唤醒时,向管道的写端 writeFD 写入一个字节的数据,内核会立即通知读端 readFD 有数据可读,从而唤醒休眠的线程。
二、新消息到来时的唤醒流程(分步骤解析)

当 Handler.sendMessage() 发送消息时,消息会通过 MessageQueue.enqueueMessage 插入队列,关键唤醒逻辑如下:

1. 消息入队:构建链表并检查是否需要唤醒
  • 消息被封装为 Message 对象,按时间戳(when)插入队列链表(可能插入头部或中间)。
  • 队列维护一个 mBlocked 标记,表示当前是否处于阻塞状态(由 Looper.loop() 调用 nativePollOnce 时设置为 true)。
  • 如果消息插入后,队列之前处于阻塞状态(mBlocked = true),则触发唤醒逻辑。
2. 唤醒信号:向管道写端发送 “字节”
  • enqueueMessage 中调用 nativeWake(mPtr)mPtr 是底层 C++ 对象的指针),该方法会向管道的写端 writeFD 写入一个字节(比如 1)。
  • 内核检测到 writeFD 有数据写入,立即向注册了 readFD 的 epoll 事件组发送 可读事件,唤醒休眠的 Looper 线程。
3. Looper 被唤醒后做什么?
  • nativePollOnce 感知到 readFD 可读,从内核层返回,Looper.loop() 恢复执行。
  • 重新遍历消息队列,取出最早到期的消息(根据 Message.when 时间戳),交给对应的 Handler 处理(调用 msg.target.dispatchMessage(msg))。
  • 处理完消息后,再次检查队列是否有后续消息:
    • 若有立即处理的消息,继续循环;
    • 若无,则再次调用 nativePollOnce,回到阻塞等待状态。
三、关键组件:管道文件描述符(Pipe FD)的作用
  • 为什么不用普通变量通知?

    • 普通变量的读写是用户态操作,无法让内核直接唤醒休眠线程;而管道是内核级资源,其状态变化(可读 / 可写)能触发内核级事件,实现 跨用户态和内核态的高效通信
  • 管道如何避免 “惊群效应”?

    • Android 中每个 Looper 对应一个独立的管道,单线程使用,不存在多个线程同时等待同一管道的问题,避免了传统 IPC 中的 “多个线程被同时唤醒” 的资源浪费。
四、实际应用:UI 事件如何被及时处理?

以用户点击屏幕为例:

  1. 触摸事件由系统服务(如 InputManagerService)捕获,封装为 Message(通过 Handler 发送到主线程的 MessageQueue)。
  2. 消息入队时,发现主线程 Looper 正在阻塞(nativePollOnce 等待中),向管道写端写入数据。
  3. 主线程被唤醒,取出触摸事件消息,交给 Activity 的 onTouchEvent 处理,更新 UI。
  4. 处理完毕后,若队列中无其他紧急消息,主线程再次进入阻塞,等待下一次事件。
五、核心优势:为什么这种机制既省电又高效?
  1. 无轮询消耗:不使用 while(true) 循环检查队列,而是依赖内核事件通知,CPU 休眠时几乎不耗电。
  2. 实时唤醒:消息入队到唤醒的延迟在 微秒级(内核级响应),确保 UI 事件和异步任务(如网络回调)及时处理,避免卡顿。
  3. 线程安全:管道操作由内核保证原子性,无需额外同步锁,减少上下文切换开销。
总结:一张图看懂唤醒流程
Java层:
Handler.sendMessage() → MessageQueue.enqueueMessage() → 检查mBlocked=true → 调用nativeWake()Native层:
nativeWake() → 向管道写端writeFD写入1字节 → 内核epoll检测到readFD可读 → 唤醒nativePollOnce()阻塞Looper.loop()恢复执行:
取出消息 → 交Handler处理 → 处理完后再次调用nativePollOnce(),等待下一次唤醒

       这种基于内核管道和事件通知的 “等待 - 唤醒” 机制,是 Android 消息系统的核心设计,既保证了主线程在空闲时的低功耗,又能在事件到来时快速响应,是实现 “流畅 UI 交互” 的底层基石。

四、消息处理:Handler.dispatchMessage 的分发逻辑

消息最终由发送它的 Handler 处理,分发流程如下:

public void dispatchMessage(Message msg) {if (msg.callback != null) { // 优先处理 Message 的 Runnable(post(Runnable))handleCallback(msg);} else if (mCallback != null) { // 其次处理 Handler 的 Callbackif (mCallback.handleMessage(msg)) {return;}}handleMessage(msg); // 最后调用用户重写的 handleMessage()
}private static void handleCallback(Message message) {message.callback.run(); // 执行 post(Runnable) 传入的 Runnable
}
  • 三种处理方式
    1. Message 自带的 Runnable:通过 post(Runnable) 发送的消息,直接执行 Runnable.run()
    2. Handler 的 Callback:通过构造函数传入的 Callback,优先级高于 handleMessage
    3. 用户重写的 handleMessage:最常用的消息处理逻辑。

五、子线程使用 Handler 的完整流程(源码级示例)

// 子线程类
class WorkerThread extends Thread {private Handler mHandler;private Looper mLooper;// 获取 Handler(供外部发送消息)public Handler getHandler() {return mHandler;}@Overridepublic void run() {// 1. 准备 Looper(创建 Looper 和 MessageQueue)Looper.prepare();synchronized (this) {mLooper = Looper.myLooper(); // 保存当前线程的 LoopermHandler = new Handler() { // 创建 Handler 关联当前 Looper@Overridepublic void handleMessage(Message msg) {// 处理消息(子线程中执行)processMessage(msg);if (msg.what == MSG_QUIT) { // 退出消息mLooper.quit(); // 停止消息循环}}};notify(); // 通知主线程 Handler 已准备好}// 2. 启动消息循环Looper.loop();}
}// 主线程使用子线程 Handler
public class MainActivity extends AppCompatActivity {private WorkerThread mWorkerThread;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mWorkerThread = new WorkerThread();mWorkerThread.start();synchronized (mWorkerThread) {try {mWorkerThread.wait(); // 等待子线程准备好 Handler} catch (InterruptedException e) {e.printStackTrace();}}// 向子线程发送消息Message msg = Message.obtain();msg.what = WorkerThread.MSG_WORK;mWorkerThread.getHandler().sendMessage(msg);// 发送退出消息mWorkerThread.getHandler().sendMessage(Message.obtain(null, WorkerThread.MSG_QUIT));}
}
  • 关键步骤
    1. 子线程中调用 Looper.prepare() 创建 Looper 和 MessageQueue。
    2. 创建 Handler 时自动关联当前 Looper(因在 prepare() 之后调用)。
    3. 调用 Looper.loop() 启动消息循环,处理外部发送的消息。
    4. 通过 Looper.quit() 终止循环,避免子线程阻塞。

六、常见问题源码级解析

1. 为什么主线程可以直接创建 Handler?
  • 主线程(ActivityThread 所在线程)在启动时,框架自动调用了:
    public static void main(String[] args) {// ...Looper.prepareMainLooper(); // 准备主线程 LooperActivityThread thread = new ActivityThread();thread.attach(false, startSeq);Looper.loop(); // 启动主线程消息循环
    }
    

    因此主线程的 Looper 已存在,无需手动调用 prepare()
2. MessageQueue 真的是 “队列” 吗?
  • 从数据结构看,它是一个 单链表,而非传统的 FIFO 队列。消息按 msg.when 排序插入,保证按时间顺序执行,支持延迟消息。
3. postDelayed 不准时的根本原因?
  • postDelayed 计算延迟的基准时间是 SystemClock.uptimeMillis()(系统启动后非休眠时间),若设备休眠,休眠时间不计入延迟,导致实际执行时间晚于预期。
  • 此外,消息需等待前面的消息处理完成,若主线程被阻塞(如耗时操作),延迟消息会被阻塞。
4. 多线程发送消息到同一 MessageQueue 为何线程安全?
  • MessageQueue.enqueueMessage 使用 synchronized (this) 加锁,确保同一时间只有一个线程操作队列,避免并发问题。

七、Handler 机制的设计精髓

  1. 线程隔离:通过 ThreadLocal 保证每个线程独有 Looper 和 MessageQueue,避免资源竞争。
  2. 消息池:重用 Message 对象,减少内存分配和 GC 压力,提升性能。
  3. 有序调度:按时间排序的消息链表,支持延迟和异步消息,灵活控制执行顺序。
  4. 阻塞与唤醒:通过 native 层实现高效的等待与唤醒,避免 CPU 空转。

总结

     Handler 机制的核心是通过 Looper(消息循环)、MessageQueue(有序消息链表)、Handler(消息收发器) 的协作,实现线程间的安全通信。源码中大量使用 ThreadLocal、synchronized、单链表等技术,确保线程隔离、数据安全和性能优化。深入理解这些细节,能帮助开发者更好地处理线程通信、避免内存泄漏(如非静态内部类 Handler 导致的 Activity 泄漏),并在复杂场景中灵活运用 Handler 机制。


http://www.mrgr.cn/news/97303.html

相关文章:

  • Android学习总结之Kotlin 协程
  • LeetCode算法题(Go语言实现)_31
  • C++自学笔记---数组和指针的异同点
  • 【计算机网络】Linux配置SNAT/DNAT策略
  • Android学习总结之算法篇五(字符串)
  • Java基础-设计模式详解
  • [项目总结] 在线OJ刷题系统项目技术应用(上)
  • 燕山大学计算机网络实验(包括实验指导书)
  • 7B斗671B:扩散模型能否颠覆自回归霸权?
  • 网络编程—TCP/IP模型(TCP协议)
  • 通过Postman和OAuth 2.0连接Dynamics 365 Online的详细步骤
  • Java 进化之路:从 Java 8 到 Java 21 的重要新特性
  • 数据库原理
  • (二)输入输出处理——打造智能对话的灵魂
  • AI Agent开发大全第二十课-如何开发一个MCP(从0开发一个MCP Server)
  • 250405-VSCode编辑launch.json实现Debug调试Open-WebUI
  • Android学习总结之应用启动流程(从点击图标到界面显示)
  • STM32F103C8T6实现 SG90 180 °舵机任意角度转动
  • 【蓝桥杯】算法笔记3
  • JJJ:generic netlink例程分析