rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • Handler

  • Handler 的设计背景:为什么需要 Handler?
  • Handler 核心组成:“四件套” 协同工作
  • Handler 工作流程:从 “发消息” 到 “处理消息” 的完整链路
    • 1. 主线程初始化 Looper(关键前提)
    • 2. 主线程创建 Handler(绑定主线程 Looper)
    • 3. 子线程发送消息(通过 Handler 入队)
    • 4. Looper 循环取消息(主线程中)
    • 5. Handler 处理消息(主线程中)
  • Handler 的核心能力与使用场景
    • 1. 子线程向主线程传递数据(核心场景)
    • 2. 延迟任务调度(postDelayed())
    • 3. 定时任务(循环执行)
    • 4. 主线程向子线程发送消息
  • Handler 的关键问题与解决方案
    • 1. 内存泄漏(最常见问题)
    • 2. 消息延迟或不执行
    • 3. 多 Handler 竞争问题
  • Handler 的替代方案(Android 后续优化)
  • Message
    • 消息结构
  • MessageQueue
    • MessageQueue.enqueueMessage()
    • MessageQueue.next()
  • 消息池
  • IdleHandler
    • IdleHandler 核心作用与使用场景
    • IdleHandler 基本使用
      • 1. 核心接口定义
      • 2. 注册与移除
    • 工作原理:与消息循环的关系
    • 注意事项与最佳实践
    • 系统中的应用案例
    • IdleHander 是如何保证不进入死循环的?
      • 1. IdleHandler 的触发依赖消息队列的 “空闲状态”
      • 2. IdleHandler 的重复执行受返回值和消息队列状态双重控制
      • 3. 与 “死循环” 的本质区别
      • 示例验证:IdleHandler 不会无限执行
      • 总结
    • framework 中如何使用 IdleHander?
      • 核心使用场景:Framework 中的典型案例
      • Framework 使用 IdleHandler 的设计原则
      • 与应用层使用的区别
      • 总结
    • 总结
  • 屏障消息
    • 消息类型
      • 1. 即时消息(Normal Message)
      • 2. 延迟消息(Delayed Message)
      • 3. 屏障消息(Barrier Message)
    • 三者的核心区别与关系
    • 什么是屏障消息?
    • 屏障消息的核心作用
    • 屏障消息的工作原理
      • 1. 消息队列中的特殊标识
      • 2. 生命周期(发送→拦截→移除)
    • Framework 中的典型应用:UI 渲染优先级控制
    • 应用层的限制与注意事项
    • 怎么创建一个异步消息?
      • 异步消息的创建方法
      • 异步消息的核心特性
      • 注意事项
      • 应用场景(应用层罕见,Framework 层常见)
  • HandlerThread
    • HandlerThread 核心作用
    • 基本使用步骤
      • 1. 创建 HandlerThread 实例
      • 2. 启动线程
      • 3. 创建子线程 Handler
      • 4. 发送消息 / 任务
      • 5. 销毁线程
    • 核心原理
    • 典型应用场景
      • 1. 后台持续任务(如轮询)
      • 2. 子线程间通信
      • 3. 替代 AsyncTask 处理后台任务
    • 注意事项
    • 总结
  • 总结
  • 问题
    • android Handler避免内存泄露handler.removeCallbacksAndMessages(null)的使用
    • Android Handler.removeMessage移除所有postDelayed的问题
  • 资料

Handler

Handler面试 | IdleHandler面试 | 屏障消息

IO模型 | ThreadLocal

在 Android 开发中,Handler 是一套用于线程间通信的核心机制,主要解决 “子线程无法直接更新 UI” 的问题,同时也承担着延迟任务调度、消息分发的角色。要深入理解 Handler,需要从其设计背景、核心原理、工作流程、使用场景及注意事项等维度展开分析。

Handler 的设计背景:为什么需要 Handler?

Android 基于 Single Thread Model(单线程模型) 设计 UI 操作,核心规则是:只有主线程(UI 线程)能修改 UI 元素,子线程(如网络请求、耗时计算线程)直接操作 UI 会抛出 CalledFromWrongThreadException。

这一规则的本质是为了避免 “多线程并发修改 UI” 导致的线程安全问题(如 UI 控件状态混乱、绘制异常)。但实际开发中,子线程常需要将结果(如网络请求数据、文件读取结果)传递给主线程更新 UI,此时就需要一套 “安全的线程通信工具”——Handler 应运而生。

Handler 核心组成:“四件套” 协同工作

Handler 并非单独运行,而是依赖 Android 系统提供的 Looper、MessageQueue、Message 三者,共同构成 “消息循环机制”。四者的角色分工如下:

组件核心作用
Handler消息的 “发送者” 和 “处理器”:负责发送 Message 到 MessageQueue,并重写 handleMessage() 处理消息。
Message线程间传递的数据载体:存储需要传递的信息(如 what 标识、obj 数据、arg1/arg2 整数)。
MessageQueue消息队列:以 “先进先出(FIFO)” 的顺序存储 Handler 发送的 Message,本质是一个链表结构。
Looper消息循环 “引擎”:不断从 MessageQueue 中取出消息,分发到对应的 Handler 进行处理,是线程的 “消息泵”。

Handler 工作流程:从 “发消息” 到 “处理消息” 的完整链路

Handler 的核心逻辑是 “消息循环”,整个流程可分为 5 个关键步骤,以 “子线程向主线程发消息更新 UI” 为例:

1. 主线程初始化 Looper(关键前提)

主线程(UI 线程)在启动时,系统会自动调用 Looper.prepareMainLooper() 完成两件事:

  • 为当前主线程创建一个 MessageQueue(消息队列);
  • 为当前主线程创建一个 Looper,并将 Looper 与主线程绑定(通过 ThreadLocal 实现,确保一个线程对应一个 Looper)。

之后,主线程会调用 Looper.loop() 启动 “消息循环”:Looper 开始无限循环,不断从 MessageQueue 中读取消息。

2. 主线程创建 Handler(绑定主线程 Looper)

开发者在主线程中创建 Handler 实例(通常在 Activity 或 Fragment 中),此时 Handler 会自动关联当前线程(主线程)的 Looper 和 MessageQueue:

// 主线程中创建 Handler
private Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 步骤 5:处理消息(主线程中执行)
        switch (msg.what) {
            case 1:
                String data = (String) msg.obj;
                textView.setText(data); // 安全更新 UI
                break;
        }
    }
};

注意:若在子线程中创建 Handler,必须先手动调用 Looper.prepare() 创建 Looper,否则会抛出 RuntimeException: Can't create handler inside thread that has not called Looper.prepare()。

3. 子线程发送消息(通过 Handler 入队)

子线程中执行耗时操作(如网络请求)后,通过 Handler 的 sendMessage()、post() 等方法发送消息:

// 子线程中发送消息
new Thread(() -> {
    // 模拟耗时操作(如网络请求)
    String result = "从服务器获取的数据";
    
    // 方式 1:发送 Message 对象
    Message msg = Message.obtain(); // 推荐:复用 Message 池,避免内存浪费
    msg.what = 1; // 消息标识(用于区分不同消息)
    msg.obj = result; // 传递数据
    mHandler.sendMessage(msg); // 将消息加入主线程的 MessageQueue
    
    // 方式 2:通过 post() 发送 Runnable(本质也是封装成 Message)
    mHandler.post(() -> {
        textView.setText(result); // Runnable 会在主线程执行
    });
}).start();
  • sendMessage():直接发送 Message 对象,需在 handleMessage() 中处理;
  • post(Runnable):将 Runnable 封装成 Message,Looper 分发后直接执行 Runnable 的 run() 方法(无需重写 handleMessage())。

4. Looper 循环取消息(主线程中)

主线程的 Looper.loop() 是一个 “无限循环”(伪代码逻辑如下),不断从 MessageQueue 中取出消息:

public static void loop() {
    Looper me = myLooper(); // 获取当前线程的 Looper
    MessageQueue queue = me.mQueue; // 获取 Looper 绑定的 MessageQueue
    
    while (true) { // 无限循环
        // 1. 从队列中取出消息(若队列空,会阻塞等待)
        Message msg = queue.next(); 
        if (msg == null) {
            return; // 队列退出,循环结束(主线程一般不会触发)
        }
        
        // 2. 将消息分发给对应的 Handler(msg.target 即发送消息的 Handler)
        msg.target.dispatchMessage(msg);
        
        // 3. 回收消息到 Message 池(复用,减少 GC)
        msg.recycleUnchecked();
    }
}
  • 关键细节:queue.next() 是阻塞操作(通过 Linux 管道和 epoll 机制实现),若 MessageQueue 中无消息,线程会进入休眠状态,避免空循环消耗 CPU;当有新消息到来时,会唤醒线程继续执行。

5. Handler 处理消息(主线程中)

Looper 调用 msg.target.dispatchMessage(msg) 后,Handler 会根据消息类型分发处理:

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        // 1. 若 Message 有 callback(即 post() 传递的 Runnable),直接执行
        handleCallback(msg); 
    } else {
        if (mCallback != null) {
            // 2. 若 Handler 构造时传入了 Callback,执行 Callback 的 handleMessage()
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 3. 最后执行 Handler 重写的 handleMessage()
        handleMessage(msg);
    }
}

至此,子线程传递的数据在主线程中被处理,UI 更新操作安全执行。

Handler 的核心能力与使用场景

除了 “线程间通信”,Handler 还支持多种实用功能,覆盖开发中的常见需求:

1. 子线程向主线程传递数据(核心场景)

如前所述,子线程执行耗时操作(网络请求、数据库读写、文件解析)后,通过 Handler 将结果传递给主线程更新 UI,避免 UI 线程阻塞。

2. 延迟任务调度(postDelayed())

通过 postDelayed(Runnable, long delayMillis) 或 sendMessageDelayed(Message, long delayMillis) 实现延迟执行任务,例如 “3 秒后自动关闭弹窗”:

// 3 秒后执行 Runnable(主线程中)
mHandler.postDelayed(() -> {
    dialog.dismiss(); // 延迟关闭弹窗
}, 3000); // 延迟时间:3000ms = 3秒

3. 定时任务(循环执行)

通过 “延迟任务中再次发送延迟任务” 实现定时循环,例如 “每隔 1 秒更新一次倒计时”:

private int count = 10;
private void startCountdown() {
    mHandler.postDelayed(() -> {
        textView.setText("倒计时:" + count--);
        if (count >= 0) {
            startCountdown(); // 再次发送延迟任务,实现循环
        }
    }, 1000);
}

注意:若需精准定时(如闹钟),建议使用 AlarmManager;Handler 定时依赖主线程消息循环,若主线程阻塞,定时会不准。

4. 主线程向子线程发送消息

虽然较少见,但 Handler 支持双向通信:若子线程手动初始化了 Looper,主线程可通过绑定该 Looper 的 Handler 向子线程发送消息,例如 “通知子线程停止耗时操作”。

Handler 的关键问题与解决方案

在使用 Handler 时,若不注意细节,容易引发 内存泄漏、消息延迟、线程安全 等问题,需重点关注:

1. 内存泄漏(最常见问题)

下图是对象之间的引用关系图,实线表示强引用,虚线表示弱引用:

网上的说法,ThreadLocal会引发内存泄露,他们的理由是这样的: 如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄露。

首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (len-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e; 如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry,否则,如果key值为null,则擦除该位置的Entry,否则继续向下一个位置查询 在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。仔细研究代码可以发现,set操作也有类似的思想,将key为null的这些Entry都删除,防止内存泄露。 但是光这样还是不够的,上面的设计思路依赖一个前提条件:要调用ThreadLocalMap的getEntry函数或者set函数。这当然是不可能任何情况都成立的,所以很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。

  • 原因:Handler 若为 Activity 的非静态内部类,会隐式持有 Activity 的引用;若 Handler 发送的消息在 Activity 销毁后仍在 MessageQueue 中未处理,Looper 会一直持有 Handler 引用,导致 Activity 无法被 GC 回收,引发内存泄漏。

  • 解决方案:

    1. 将 Handler 定义为 静态内部类(静态内部类不持有外部类引用);
    2. 通过 WeakReference(弱引用)持有 Activity 或 TextView,避免强引用;
    3. 在 Activity 的 onDestroy() 中调用 mHandler.removeCallbacksAndMessages(null),移除所有未处理的消息和 Runnable。

    示例代码(安全的 Handler 写法):

    // 1. 静态内部类 + WeakReference
    private static class SafeHandler extends Handler {
        private WeakReference<MainActivity> mActivityRef;
    
        public SafeHandler(MainActivity activity) {
            mActivityRef = new WeakReference<>(activity);
        }
    
        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity activity = mActivityRef.get();
            if (activity != null && !activity.isFinishing()) {
                // 2. 确认 Activity 存活后再操作
                switch (msg.what) {
                    case 1:
                        activity.textView.setText((String) msg.obj);
                        break;
                }
            }
        }
    }
    
    // 3. Activity 中创建 Handler
    private SafeHandler mHandler = new SafeHandler(this);
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 4. 移除所有未处理的消息和 Runnable
        mHandler.removeCallbacksAndMessages(null);
    }
    

2. 消息延迟或不执行

  • 可能原因:
    1. 主线程阻塞:若主线程执行了耗时操作(如 onCreate() 中循环计算),会导致 Looper 无法及时处理消息,出现延迟;
    2. 消息被移除:若调用了 removeCallbacks() 或 removeMessages(),导致目标消息被提前移除;
    3. Looper 退出:子线程的 Looper 若被调用 quit(),MessageQueue 会清空,后续消息无法执行。
  • 解决方案:
    • 避免主线程执行耗时操作,耗时逻辑全部放入子线程;
    • 检查是否误调用了消息移除方法;
    • 子线程中若需长期使用 Handler,避免过早调用 Looper.quit()。

3. 多 Handler 竞争问题

若多个 Handler 绑定同一个 Looper(如主线程),MessageQueue 会按 “发送时间” 顺序处理消息,不存在竞争问题;但需注意:Handler 的 handleMessage() 是在 Looper 线程中执行的,若某个消息处理耗时,会阻塞后续所有消息(包括其他 Handler 的消息)。

例如:主线程中一个 Handler 的 handleMessage() 执行了 5 秒的耗时操作,期间其他 Handler 发送的 “更新 UI” 消息会被阻塞,导致界面卡顿。

Handler 的替代方案(Android 后续优化)

随着 Android 版本演进,官方提供了一些更简洁的工具替代 Handler 的部分场景,核心目标是 “减少模板代码、避免内存泄漏”:

替代方案适用场景优势
ViewModel + LiveData子线程向主线程传递数据(MVVM 架构)自动感知生命周期,避免内存泄漏;无需手动处理消息。
Coroutine(协程)异步任务(网络请求、耗时计算)+ 线程切换用同步代码写异步逻辑,简化线程切换(Dispatchers.Main 直接切主线程)。
WorkManager延迟 / 定时任务(需后台稳定执行)支持系统重启后继续执行,适配低电量、后台限制。
HandlerCompat兼容不同 Android 版本的 Handler 操作封装了版本差异(如 postDelayed() 兼容),避免 API 适配问题。

但需注意:这些方案的底层仍依赖 Handler 机制(如 LiveData、Coroutine 最终还是通过 Handler 切换到主线程),Handler 仍是 Android 线程通信的 “底层基石”,理解其原理对排查问题(如卡顿、内存泄漏)至关重要。

Message

消息结构

每个消息用Message表示,Message主要包含以下内容:

filed含义说明
what消息类别由用户定义,用来区分不同的消息
arg1参数1是一种轻量级的传递数据的方式
arg2参数2是一种轻量级的传递数据的方式
obj消息内容任意对象,但是使用Messenger跨进程传递Message时不能为null
dataBundle数据比较复杂的数据建议使用该变量(相比上面几个,这个县的比较重量级)
target消息响应方关联的Handler对象,处理Message时会调用它分发处理Message对象
when触发响应时间处理消息时间
nextMessage队列里的下一个Message对象用next指向下一条Message,实现一个链表数据结构,用户一般使用不到该字段。

一般不用手动设置target,调用Handler.obtainMessage()方法会自动的设置Message的target为当前的Handler。 得到Message之后可以调用sendToTarget(),发送消息给Handler,Handler再把消息放到message queue的尾部。 对Message除了给部分成员变量赋值外的操作都可以交由Handler来处理。

MessageQueue

MessageQueue.enqueueMessage()

这个方法是所有消息发送方法最终调用的终点,也就是说无论怎么发送消息,都会直接插入到对应的消息队列中去。并且在插入后还会根据一些判断,来决定是否唤醒阻塞的队列。

boolean enqueueMessage(Message msg, long when) {
    
    ...

    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) {
            // 队列里无消息,或插入消息的执行时间为0(强制插入队头),或插入消息的执行
            // 时间先于队头消息,这三种情况下插入消息为新队头,如果队列被阻塞则将其唤醒
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 根据执行时间将消息插入到队列中间。通常我们不必唤醒事件队列,除非
            // 队列头部有消息屏障阻塞队列,并且插入的消息是队列中第一个异步消息
            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;
        }
        
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            // Native方法唤醒等待的线程
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue.next()

该方法可以从消息队列中取出一个需处理的消息,在没有消息或者消息还未到时时,该方法会阻塞线程,等待消息通过 MessageQueue.enqueueMessage() 方法入队后唤醒线程。

Message next() {
    
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        
        // 通过native层的MessageQueue阻塞nextPollTimeoutMillis毫秒时间
        nativePollOnce(ptr, nextPollTimeoutMillis);
        
        synchronized (this) {
            // 尝试检索下一个消息,如果找到则返回该消息
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // 被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 {
                    // 从队列中获取一个要执行的消息
                    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 {
                // 队列中没有消息,一直阻塞等待唤醒
                nextPollTimeoutMillis = -1;
            }
            
            ...
            
            // 如果第一次遇到空闲状态,则获取要运行的IdleHandler数量
            // 仅当队列为空或者队列中的第一条消息(可能是同步屏障)
            // 还没到执行时间时,才会执行IdleHandler
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 如果没有IdleHandler需要执行,那么就阻塞等待下一个消息到来
                mBlocked = true;
                continue;
            }
            
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        
        // 运行IdleHandler
        // 执行一次next方法只有第一次轮询能执行这里的操作
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            
            boolean keep = false;
            try {
                // 执行IdleHandler,返回是否保留IdleHandler
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            
            if (!keep) {
                synchronized (this) {
                    // 如果不需要保留,则移除这个IdleHandler
                    mIdleHandlers.remove(idler);
                }
            }
        }
        
        // 设置为0保证在再次执行next方法之前不会再执行IdleHandler
        pendingIdleHandlerCount = 0;
        
        // 当调用一个IdleHandler执行后,无需等待直接再次检索一次消息队列
        nextPollTimeoutMillis = 0;
    }
}

上面源码里面的方法 nativePollOnce(ptr, nextPollTimeoutMillis) 是一个 Native 方法,实际作用就是通过 Native 层的 MessageQueue 阻塞 nextPollTimeoutMillis 毫秒的时间。

  • 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时。
  • 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回。
  • 如果 nextPollTimeoutMillis > 0,最长阻塞 nextPollTimeoutMillis 毫秒(超时),如果期间有程序唤醒会立即返回。

消息池

在通过Handler发送消息时,我们可以通过代码Message message = new Message();新建一条消息,但是我们并不推荐这样做,因为这样每次都会新建一条消息,很容易造成资源浪费。Android中设计了消息池用于避免该现象:

  • 获取消息 obtain() 从消息池中获取消息: Message msg = Message.obtain(); obtain()方法源码:
public static Message obtain() {
    synchronized (sPoolSync) {
      if (sPool != null) {
        Message m = sPool;
        sPool = m.next;
        m.next = null; //从sPool中取出一个Message对象,并消息链表断开
        m.flags = 0; // clear in-use flag清除in-use flag
        sPoolSize--;//消息池的可用大小进行-1操作
        return m;
      }
    }
    return new Message();// 当消息池为空时,直接创建Message对象
}

IdleHandler

IdleHandler 是 Android 系统提供的一种轻量级机制,用于在 消息队列空闲时执行延迟任务,核心价值是在不阻塞主线程的前提下,利用系统空闲时间处理低优先级任务(如预加载数据、优化 UI 等)。

IdleHandler 说白了,就是在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机制。

IdleHandler 核心作用与使用场景

IdleHandler 属于 MessageQueue 的内部接口,当消息队列中 暂时没有待处理的消息(或消息执行完毕、等待延迟消息)时,会触发 IdleHandler 的回调。

核心特点:

  • 任务在主线程执行,但仅在队列空闲时触发,不会阻塞紧急消息(如用户输入、UI 更新)。
  • 适合执行 低优先级、非紧急任务(如预加载缓存、回收资源、日志上报)。

典型使用场景:

  1. 页面初始化完成后,利用空闲时间预加载下一页数据。
  2. 清理内存缓存、释放临时资源(不影响用户交互)。
  3. 统计页面渲染完成后的性能数据(如首屏加载时间)。

IdleHandler 基本使用

1. 核心接口定义

IdleHandler 接口仅含一个方法,返回值决定是否重复执行:

public interface IdleHandler {
    /**
     * 消息队列空闲时回调
     * @param remainingIdleTimeMillis 预估的空闲时间(毫秒),-1 表示无更多消息
     * @return true:保留此 IdleHandler,下次空闲时继续回调;false:执行后移除
     */
    boolean queueIdle(long remainingIdleTimeMillis);
}

2. 注册与移除

通过 MessageQueue 的 addIdleHandler() 注册,removeIdleHandler() 移除:

// 获取主线程的 MessageQueue
MessageQueue queue = Looper.myQueue();

// 创建 IdleHandler 实例
IdleHandler myIdleHandler = remainingIdleTime -> {
    // 执行空闲任务(如预加载)
    preloadNextPageData();
    
    // 返回 false:执行一次后移除;true:保留,下次空闲继续执行
    return false;
};

// 注册 IdleHandler
queue.addIdleHandler(myIdleHandler);

// (可选)在不需要时移除(如页面销毁)
// queue.removeIdleHandler(myIdleHandler);

工作原理:与消息循环的关系

IdleHandler 的触发时机与 Looper 的消息循环紧密相关,结合 MessageQueue.next() 方法理解:

  1. 消息队列处理流程:
    • Looper.loop() 循环调用 MessageQueue.next() 获取下一条消息。
    • 若队列中无消息或消息未到执行时间(延迟消息),next() 会进入阻塞状态。
    • 在阻塞前,系统会遍历所有已注册的 IdleHandler,依次调用 queueIdle()。
  2. 触发条件:
    • 队列中 没有即时消息(所有消息均为延迟消息且未到执行时间)。
    • 队列中 所有消息已处理完毕(next() 返回 null 前,仅主线程退出时出现)。
  3. 与延迟任务的区别:
    • Handler.postDelayed():强制延迟固定时间执行,无论队列是否繁忙。
    • IdleHandler:仅在队列空闲时执行,若主线程一直繁忙(如频繁处理消息),可能永远不触发。

注意事项与最佳实践

  1. 避免耗时操作

    IdleHandler 在主线程执行,若 queueIdle() 中处理耗时任务(如 IO 操作、复杂计算),会导致主线程卡顿。建议仅执行轻量级任务(如内存数据处理)。

  2. 控制重复执行

    若返回 true(保留回调),需在合适时机调用 removeIdleHandler() 移除,否则会持续占用系统资源(尤其在频繁空闲的场景下)。

  3. 配合生命周期管理

    在 Activity/Fragment 中使用时,需在 onDestroy() 中移除 IdleHandler,避免内存泄漏(防止回调持有组件引用)。

    示例(安全使用方式):

    public class MyActivity extends Activity {
        private IdleHandler myIdleHandler;
        private MessageQueue queue;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            queue = Looper.myQueue();
            myIdleHandler = remainingTime -> {
                // 执行空闲任务
                return false; // 仅执行一次
            };
            queue.addIdleHandler(myIdleHandler);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 移除 IdleHandler,避免内存泄漏
            if (queue != null && myIdleHandler != null) {
                queue.removeIdleHandler(myIdleHandler);
            }
        }
    }
    
  4. 谨慎依赖执行时机

    由于 IdleHandler 触发时间不确定(可能延迟甚至不执行),不可用于紧急任务(如用户操作后的即时反馈)。

系统中的应用案例

Android 框架内部广泛使用 IdleHandler 处理低优先级任务:

  • View 绘制优化:ViewRootImpl 利用 IdleHandler 在布局绘制完成后,执行一些清理工作。
  • 资源回收:在系统空闲时,触发部分缓存资源的回收(如图片缓存 LRU 清理)。
  • 性能监控:统计消息队列空闲时间,评估主线程负载情况。

IdleHander 是如何保证不进入死循环的?

IdleHandler 之所以不会导致死循环,核心在于在于它的触发机制与 消息队列(MessageQueue)的工作流程深度绑定,仅在队列 “真正空闲” 时执行,且受消息队列的状态控制。具体原因可从以下三个方面解析:

1. IdleHandler 的触发依赖消息队列的 “空闲状态”

IdleHandler 的回调(queueIdle())并非独立运行,而是作为 MessageQueue 消息处理流程的一部分 被触发。其执行时机是在 MessageQueue.next() 方法中 —— 当队列中没有 “即时需要处理的消息” 时才会调用。

MessageQueue.next() 是消息循环的核心方法(被 Looper.loop() 循环调用),其简化逻辑如下:

Message next() {
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;

    for (;;) { // 消息队列的主循环
        // 1. 阻塞等待消息(根据 nextPollTimeoutMillis 决定等待时间)
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // 2. 检查是否有即时消息(已到执行时间的消息)
            long now = SystemClock.uptimeMillis();
            Message msg = mMessages;
            if (msg != null && msg.when <= now) {
                // 有即时消息:取出并返回,退出本次循环
                return msg;
            } else {
                // 无即时消息:准备执行 IdleHandler
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
        }

        // 3. 执行所有 IdleHandler(仅当队列空闲时)
        if (pendingIdleHandlerCount > 0) {
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mIdleHandlers.get(i);
                // 调用回调,返回值决定是否保留
                boolean keep = idler.queueIdle(nextPollTimeoutMillis);
                if (!keep) {
                    mIdleHandlers.remove(i); // 不保留则移除
                }
            }
            pendingIdleHandlerCount = 0;
        }

        // 4. 计算下次阻塞等待的时间(根据延迟消息的剩余时间)
        nextPollTimeoutMillis = calculatePollTimeout();
        if (nextPollTimeoutMillis != 0) {
            // 有延迟消息:跳出循环,进入阻塞等待
            break;
        }
        // 无延迟消息:继续循环(但此时队列已空,最终会阻塞)
    }
}

从逻辑可知:

IdleHandler 仅在 队列中没有即时消息 时才会执行,且执行后会根据 “是否有延迟消息” 决定下一步:

  • 若有延迟消息:计算等待时间后进入阻塞(nativePollOnce),不会再次触发 IdleHandler。
  • 若无延迟消息:队列已空,最终会进入永久阻塞(等待新消息到来),也不会重复执行。

2. IdleHandler 的重复执行受返回值和消息队列状态双重控制

IdleHandler.queueIdle() 的返回值(boolean)决定是否保留该回调,但即使返回 true(保留),也不会无限制触发,因为:

  1. 需再次满足 “队列空闲” 条件

    只有当消息队列再次进入 “无即时消息” 状态时,才会重新执行保留的 IdleHandler。若后续有新消息加入队列(如用户操作产生的 UI 消息),next() 会优先处理新消息,IdleHandler 不会被触发。

  2. 消息队列的阻塞机制避免空循环

    若队列中一直无消息,nextPollTimeoutMillis 会被设为 -1,导致 nativePollOnce 进入 永久阻塞(线程挂起),直到有新消息通过 enqueueMessage() 加入队列并唤醒线程。此时整个消息循环处于休眠状态,IdleHandler 自然不会执行。

3. 与 “死循环” 的本质区别

死循环的典型特征是 “无条件重复执行,不依赖外部状态,消耗 CPU 资源”,而 IdleHandler 完全不满足这些:

  • 依赖外部状态:仅当消息队列空闲时触发,有消息则优先处理消息。
  • 不消耗 CPU:若队列无消息,线程会进入阻塞(nativePollOnce),释放 CPU 资源,而非空循环。
  • 可控的重复执行:即使返回 true,也需等待队列再次空闲,且新消息会中断这一过程。

示例验证:IdleHandler 不会无限执行

Looper.myQueue().addIdleHandler(remainingTime -> {
    System.out.println("IdleHandler 执行,剩余空闲时间:" + remainingTime);
    return true; // 保留回调,期望重复执行
});

// 1秒后发送一条消息(模拟用户操作)
new Handler(Looper.getMainLooper()).postDelayed(() -> {
    System.out.println("收到新消息,处理中...");
}, 1000);

执行结果:

IdleHandler 执行,剩余空闲时间:-1  // 初始队列空闲,触发一次
收到新消息,处理中...            // 1秒后新消息到来,优先处理
IdleHandler 执行,剩余空闲时间:-1  // 新消息处理完毕,队列再次空闲,再次触发

可见,IdleHandler 仅在 “无消息处理” 时执行,且新消息会中断其连续执行,完全不会形成死循环。

总结

IdleHandler 不进入死循环的核心原因是:

  1. 其触发依赖 消息队列的空闲状态,有消息时优先处理消息;
  2. 消息队列通过 nativePollOnce 实现 阻塞等待,无消息时线程休眠,不消耗 CPU;
  3. 即使保留回调(返回 true),也需等待队列再次空闲,且新消息会中断这一过程。

这种设计让 IdleHandler 既能利用空闲时间处理低优先级任务,又不会干扰主线程的正常消息处理。

framework 中如何使用 IdleHander?

在 Android Framework 源码中,IdleHandler 被广泛用于利用主线程空闲时间处理低优先级任务,例如资源回收、延迟初始化、性能监控等场景。其核心思路是在不阻塞用户交互的前提下,“见缝插针针” 地执行非紧急操作。以下从 Framework 关键模块的使用案例出发,解析其设计思想。

核心使用场景:Framework 中的典型案例

1. Activity 启动优化:延迟初始化非关键资源

在 ActivityThread(Activity 启动的核心类)中,IdleHandler 被用于在 Activity 启动完成后,利用空闲时间执行一些非必需的初始化操作,避免阻塞启动流程。

源码简化示例:

// ActivityThread 中处理 Activity 启动的逻辑
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 1. 执行 Activity 启动的核心流程(onCreate、onStart 等)
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        // 2. 启动完成后,注册 IdleHandler 处理后续任务
        Looper.myQueue().addIdleHandler(new IdleHandler() {
            @Override
            public boolean queueIdle(long remainingIdleTimeMillis) {
                // 3. 空闲时执行:如清理启动临时资源、报告启动完成事件
                r.activity.performPostLaunchProcessing();
                // 4. 执行一次后移除
                return false;
            }
        });
    }
}

作用:确保 Activity 关键生命周期(如 onCreate)优先执行,非紧急的 “启动后处理” 在主线程空闲时执行,提升启动速度感知。

2. View 系统:布局绘制后的清理与优化

ViewRootImpl(负责 View 绘制的核心类)使用 IdleHandler 在 View 树绘制完成后,利用空闲时间执行缓存清理、性能统计等操作。

源码简化示例:

// ViewRootImpl 中触发 View 绘制的逻辑
void performTraversals() {
    // 1. 执行 View 绘制流程(measure、layout、draw)
    performMeasure();
    performLayout();
    performDraw();
    
    // 2. 绘制完成后,注册 IdleHandler 处理后续优化
    mChoreographer.postIdleMessage(new Runnable() {
        @Override
        public void run() {
            // 3. 空闲时执行:如回收绘制临时对象、统计绘制耗时
            cleanupAfterDraw();
            reportDrawPerformance();
        }
    });
    // (注:Choreographer 的 postIdleMessage 内部依赖 IdleHandler 实现)
}

作用:避免绘制过程中执行额外操作导致卡顿,确保 UI 渲染优先完成。

3. 资源管理:空闲时回收内存缓存

Framework 的资源管理模块(如 AssetManager、图片缓存)使用 IdleHandler 在主线程空闲时清理过期资源,释放内存。

源码简化示例:

// 资源缓存管理类
class ResourceCache {
    private final LruCache<String, Resource> mCache = new LruCache<>(100);
    
    public ResourceCache() {
        // 注册 IdleHandler 定期清理过期资源
        Looper.myQueue().addIdleHandler(new IdleHandler() {
            @Override
            public boolean queueIdle(long remainingIdleTimeMillis) {
                // 1. 清理超过 5 分钟未使用的资源
                mCache.trimToSize(50); // 缩小缓存容量
                // 2. 保留回调,下次空闲继续检查
                return true;
            }
        });
    }
}

作用:在不影响用户交互的情况下,定期回收闲置资源,平衡内存占用与性能。

4. 系统服务:延迟处理低优先级任务

系统服务(如 NotificationManagerService、PackageManagerService)使用 IdleHandler 处理非紧急任务,例如日志上报、后台同步。

示例:NotificationManagerService 在通知展示后,利用空闲时间记录通知曝光日志:

// 通知展示后的处理
private void postNotification(StatusBarNotification sbn) {
    // 1. 优先展示通知(关键操作)
    mStatusBar.showNotification(sbn);
    
    // 2. 注册 IdleHandler 记录曝光日志(非紧急)
    Looper.myQueue().addIdleHandler(new IdleHandler() {
        @Override
        public boolean queueIdle(long remainingIdleTimeMillis) {
            logNotificationImpression(sbn); // 记录曝光事件
            return false;
        }
    });
}

作用:确保核心功能(如通知展示)即时响应,非关键操作(如日志)延迟执行。

Framework 使用 IdleHandler 的设计原则

从上述案例可总结出 Framework 对 IdleHandler 的使用遵循以下原则:

  1. 优先级隔离:

    核心任务(如 Activity 启动、UI 绘制)优先执行,非核心任务(如日志、缓存清理)通过 IdleHandler 延迟到空闲时执行,避免相互干扰。

  2. 轻量操作:

    IdleHandler 中执行的任务均为轻量级(如内存操作、简单计算),避免耗时逻辑(如 IO 操作),防止阻塞主线程。

  3. 生命周期管理:

    临时任务(如单次清理)返回 false 自动移除;长期任务(如定期检查)返回 true 并在合适时机主动移除(如服务销毁时),避免内存泄漏。

  4. 依赖空闲状态:

    仅当主线程真正空闲(无用户输入、无 UI 更新)时才执行,确保用户交互不受影响。

与应用层使用的区别

Framework 对 IdleHandler 的使用与应用层相比,更注重系统级稳定性和性能:

  • 更严格的轻量性:Framework 任务会严格控制执行时间(通常 <1ms),避免影响系统响应速度。
  • 全局资源协调:多个系统模块的 IdleHandler 会通过消息队列的执行顺序自然协调,避免资源竞争。
  • 与底层机制结合:常与 Choreographer(帧率控制)、Looper 消息优先级等机制配合,实现更精细的调度。

总结

在 Android Framework 中,IdleHandler 是主线程资源调度的重要工具,其核心价值是在不影响用户体验的前提下,利用空闲时间处理低优先级任务。典型场景包括:

  • 启动流程的后续清理;
  • UI 绘制后的优化操作;
  • 内存缓存的定期回收;
  • 系统服务的非紧急任务。

这种设计体现了 Android 对 “用户体验优先” 的追求 —— 确保关键操作即时响应,非关键操作 “见缝插针” 地执行。

总结

IdleHandler 是一种 “见缝插针” 式的任务调度机制,核心价值是 利用主线程空闲时间处理非紧急任务,避免占用关键交互的资源。使用时需注意:

  • 任务必须轻量,不阻塞主线程;
  • 结合组件生命周期管理,及时移除回调;
  • 不依赖其执行时机,仅用于低优先级场景。

合理使用 IdleHandler 可在不影响用户体验的前提下,提升应用性能和资源利用率。

屏障消息

消息类型

在 Android 消息机制(Handler-Looper-MessageQueue)中,从消息的触发时机和优先级角度,可将消息分为以下三种类型:

Android 消息机制的三种消息分别对应不同的调度需求:

  • 即时消息处理普通任务;
  • 延迟消息处理定时任务;
  • 屏障消息(配合异步消息)处理高优先级任务(如 UI 渲染),确保关键操作不被阻塞。

其中,屏障消息是 Framework 内部实现流畅 UI 的核心机制,应用层通常无需关注,但理解其原理有助于深入掌握 Android 性能优化(如避免 UI 卡顿)。

1. 即时消息(Normal Message)

  • 定义:无需延迟,发送后立即进入消息队列等待执行的消息,是最常见的消息类型。
  • 触发时机:发送后会被 MessageQueue 按发送顺序(FIFO)排序,一旦轮到它就会被 Looper 取出并执行。
  • 发送方式:通过 Handler.sendMessage(Message) 或 Handler.post(Runnable) 发送。
  • 典型场景:子线程向主线程发送 UI 更新指令(如更新 TextView 文本)、主线程内的普通任务调度。
// 发送即时消息
handler.sendMessage(Message.obtain(handler, 0, "即时消息"));
// 或通过 post 发送 Runnable(本质也是即时消息)
handler.post(() -> textView.setText("更新UI"));

2. 延迟消息(Delayed Message)

  • 定义:指定延迟时间(delayMillis)后才执行的消息,消息队列会按延迟后的执行时间排序。
  • 触发时机:发送时会计算实际执行时间(SystemClock.uptimeMillis() + delayMillis),MessageQueue 会确保在到达该时间后才将其交给 Looper 处理。
  • 发送方式:通过 Handler.sendMessageDelayed(Message, long) 或 Handler.postDelayed(Runnable, long) 发送。
  • 典型场景:延迟执行任务(如 3 秒后关闭弹窗、定时刷新页面)。
// 延迟 3 秒执行
handler.postDelayed(() -> dialog.dismiss(), 3000);

3. 屏障消息(Barrier Message)

  • 定义:一种特殊的 “拦截消息”,用于阻塞队列中所有普通 / 延迟消息,仅允许异步消息(Async Message) 通过,是系统级的消息优先级控制机制。
  • 触发时机:发送后会成为队列的 “屏障”,后续的普通消息会被阻塞,直到屏障被移除或有异步消息到达执行时间。
  • 发送与移除:
    • 发送:通过 MessageQueue.postSyncBarrier()(系统隐藏 API,应用层无法直接调用,仅 Framework 内部使用)。
    • 移除:必须通过 MessageQueue.removeSyncBarrier(int token) 移除,否则会导致普通消息永久阻塞。
  • 典型场景:UI 渲染优先级控制(如 Choreographer 用于确保 Vsync 信号触发的渲染任务优先执行,避免被其他消息阻塞导致掉帧)。
// Framework 内部使用示例(应用层无法直接调用)
// 1. 发送屏障消息,返回屏障 token
int barrierToken = mQueue.postSyncBarrier();
// 2. 发送异步消息(会穿过屏障)
Message asyncMsg = Message.obtain(handler, () -> performDraw());
asyncMsg.setAsynchronous(true); // 标记为异步消息
handler.sendMessage(asyncMsg);
// 3. 任务完成后移除屏障
mQueue.removeSyncBarrier(barrierToken);

三者的核心区别与关系

类型优先级特点典型使用方
即时消息普通按顺序执行,无延迟应用层、Framework
延迟消息普通到达延迟时间后执行,按时间排序应用层、Framework
屏障消息最高阻塞普通消息,仅允许异步消息通过仅 Framework(如 Choreographer)
  • 屏障消息的特殊性:它本身不承载任务,仅作为 “开关” 控制消息队列的执行逻辑,目的是确保高优先级异步消息(如 UI 渲染)不被普通消息阻塞。
  • 异步消息与屏障的配合:异步消息需通过 Message.setAsynchronous(true) 标记,才能在屏障存在时被执行,是 Framework 保证 UI 流畅性的关键机制。

什么是屏障消息?

在 Android 消息机制中,屏障消息(SyncBarrier,同步屏障) 是一种特殊的消息类型,用于阻塞普通消息执行,仅允许异步消息(Async Message)通过,以此保证高优先级任务(如 UI 渲染)的即时响应。它是 Framework 层实现流畅 UI 体验的核心机制之一,应用层通常无法直接使用,但理解其原理对分析 UI 性能问题至关重要。

屏障消息是 Android 消息机制中用于优先级控制的特殊消息,通过阻塞普通消息、放行异步消息,确保高优先级任务(如 UI 渲染)的即时执行。其核心特点是:

  • 由 target = null 标识,仅 Framework 层可发送和移除;
  • 与异步消息配合,是实现流畅 UI 的关键机制;
  • 应用层无法直接使用,但理解其原理有助于分析 UI 卡顿问题(如屏障未及时移除导致的消息阻塞)。

屏障消息的设计体现了 Android 对 “用户体验优先” 的底层优化 —— 确保用户可见的 UI 操作始终获得最高优先级。

屏障消息的核心作用

屏障消息本身不承载任何业务逻辑,仅作为消息队列中的 “拦截器”,其核心功能是:

  • 阻塞所有普通消息:一旦屏障消息进入队列,后续的普通消息(非异步)会被暂时阻塞,无法被 Looper 取出执行。
  • 放行异步消息:被标记为 “异步” 的消息(通过 Message.setAsynchronous(true))可以绕过屏障,正常执行。

这种机制的目的是确保高优先级任务(如屏幕渲染、用户输入)不被低优先级任务(如日志打印、数据统计)阻塞,从而避免 UI 卡顿。

屏障消息的工作原理

1. 消息队列中的特殊标识

屏障消息本质是一个 Message 对象,但具有以下特殊性:

  • target 为 null:普通消息的 target 是发送它的 Handler,而屏障消息的 target = null(这是消息队列识别屏障的关键)。
  • 仅用于拦截:自身不会被执行,仅控制其他消息的执行顺序。

2. 生命周期(发送→拦截→移除)

屏障消息的完整流程由三个核心步骤构成:

(1)发送屏障消息

通过 MessageQueue.postSyncBarrier() 发送,返回一个唯一的 token(用于后续移除屏障):

// Framework 源码中的实现(应用层无法直接调用,被 @hide 标记)
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        // 创建屏障消息(target = null)
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token; // 存储 token 用于标识屏障

        // 将屏障消息插入消息队列(按 when 时间排序)
        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) {
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token; // 返回 token,用于移除屏障
    }
}

(2)拦截普通消息,放行异步消息

当 MessageQueue.next()(Looper 循环取消息的方法)遇到屏障消息时,会跳过所有普通消息,只查找异步消息:

// MessageQueue.next() 核心逻辑(简化)
Message next() {
    for (;;) {
        // 阻塞等待消息
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            long now = SystemClock.uptimeMillis();
            Message msg = mMessages;
            
            // 遇到屏障消息(target = null)
            if (msg != null && msg.target == null) {
                // 跳过所有普通消息(target != null),只找异步消息(isAsynchronous() = true)
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            // 找到异步消息或非屏障消息,返回执行
            if (msg != null) {
                return msg;
            }
        }
    }
}

(3)移除屏障消息

屏障消息必须手动移除(否则会永久阻塞普通消息),通过 MessageQueue.removeSyncBarrier(int token) 实现:

// Framework 源码中的实现
public void removeSyncBarrier(int token) {
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        // 遍历队列找到对应的屏障消息(通过 token 匹配)
        while (p != null && !(p.target == null && p.arg1 == token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + "barrier token has not been posted or has already been removed.");
        }
        // 从队列中移除屏障消息
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked(); // 回收屏障消息
        // 若需要,唤醒阻塞的 Looper
        if (needWake) {
            nativeWake(ptr);
        }
    }
}

Framework 中的典型应用:UI 渲染优先级控制

屏障消息最核心的应用是 Choreographer( choreographer 意为 “编舞者”),它负责协调 UI 渲染的三大步骤(测量、布局、绘制),确保与屏幕刷新率(如 60fps)同步,避免掉帧。

工作流程:

  1. 当系统发出 Vsync 信号(屏幕刷新触发信号)时,Choreographer 会先发送一个屏障消息到主线程队列,阻塞所有普通消息。
  2. 然后发送异步消息(标记为 isAsynchronous(true)),携带渲染任务(doFrame())。
  3. 由于屏障消息的存在,普通消息被阻塞,渲染任务(异步消息)优先执行,保证 UI 及时刷新。
  4. 渲染完成后,Choreographer 立即移除屏障消息,普通消息恢复执行。

这种机制确保了 UI 渲染任务不会被其他低优先级任务(如网络回调、日志打印)阻塞,是 Android 流畅度的关键保障。

应用层的限制与注意事项

  1. 无法直接使用:

    发送和移除屏障消息的 postSyncBarrier() 和 removeSyncBarrier() 是 @hide 标记的系统 API,应用层无法直接调用(反射调用可能导致系统不稳定或在高版本 Android 中失效)。

  2. 滥用的风险:

    若屏障消息未被及时移除,会导致普通消息永久阻塞,引发 ANR(应用无响应)或功能异常。Framework 层对屏障的使用有严格的生命周期管理(如 Choreographer 会在渲染完成后立即移除)。

  3. 与异步消息的配合:

    屏障消息仅对异步消息有效,应用层若创建异步消息(通过 setAsynchronous(true)),在无屏障时与普通消息行为一致,不会有特殊优先级。

怎么创建一个异步消息?

在 Android 消息机制中,异步消息(Async Message) 是一种特殊消息,它能在屏障消息(SyncBarrier) 存在时绕过阻塞,优先执行。这种消息主要用于 Framework 层保证高优先级任务(如 UI 渲染)的执行,应用层也可创建,但需注意使用场景。

创建异步消息的核心是通过 setAsynchronous(true) 标记,或使用带 async 参数的 Handler 构造方法。其特殊价值在于能绕过屏障消息执行,但应用层需谨慎使用,避免干扰系统消息调度。理解异步消息的原理,有助于深入掌握 Android UI 渲染机制和性能优化。

异步消息的创建方法

创建异步消息的核心是通过 Message.setAsynchronous(true) 标记消息为异步,具体有以下两种方式:

1. 直接操作 Message 对象

通过 Message.obtain() 获取消息后,调用 setAsynchronous(true) 标记为异步,再通过 Handler 发送:

// 1. 创建 Handler(主线程或子线程均可)
Handler handler = new Handler(Looper.myLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 处理异步消息
        Log.d("AsyncMessage", "收到异步消息:" + msg.obj);
    }
};

// 2. 创建并标记异步消息
Message asyncMsg = Message.obtain(handler);
asyncMsg.what = 1; // 消息标识
asyncMsg.obj = "我是异步消息";
asyncMsg.setAsynchronous(true); // 关键:标记为异步消息

// 3. 发送消息(支持即时、延迟等发送方式)
handler.sendMessage(asyncMsg);
// 也可发送延迟异步消息
// handler.sendMessageDelayed(asyncMsg, 1000);
2. 通过 Handler 构造方法指定异步

创建 Handler 时,通过 Handler(Looper, Callback, boolean async) 构造方法,指定 async = true,该 Handler 发送的所有消息默认均为异步消息:

// 1. 创建默认发送异步消息的 Handler
Handler asyncHandler = new Handler(Looper.myLooper(), new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        Log.d("AsyncMessage", "收到默认异步消息:" + msg.obj);
        return true;
    }
}, true); // 第三个参数为 true:该 Handler 发送的消息默认异步

// 2. 直接发送消息(无需手动标记,自动为异步)
Message msg = Message.obtain(asyncHandler);
msg.obj = "默认异步消息";
asyncHandler.sendMessage(msg);

异步消息的核心特性

  1. 与屏障消息配合:

    当消息队列中存在屏障消息(SyncBarrier) 时,普通消息会被阻塞,而异步消息可绕过屏障继续执行(这是异步消息的核心价值)。

  2. 应用层与 Framework 层的区别:

    • Framework 层:广泛用于 UI 渲染(如 Choreographer 发送的 Vsync 回调消息),通过屏障消息确保渲染任务不被普通消息阻塞。
    • 应用层:很少需要手动创建,若滥用可能干扰系统消息调度(如导致 UI 卡顿)。

注意事项

  1. 屏障消息的权限:

    发送屏障消息的 MessageQueue.postSyncBarrier() 是系统隐藏 API(@hide),应用层无法直接调用(反射调用可能导致兼容性问题)。因此,应用层创建的异步消息在无屏障时,与普通消息行为一致。

  2. 避免滥用:

    异步消息的优先级高于普通消息,若应用层大量使用,可能抢占系统关键消息(如用户输入)的执行机会,影响体验。

  3. 线程安全:

    异步消息仍在 Handler 绑定的线程(如主线程)执行,需避免在消息处理中执行耗时操作,否则会阻塞线程。

应用场景(应用层罕见,Framework 层常见)

  • Framework 层:Choreographer 用于处理屏幕刷新(Vsync 信号),通过屏障消息 + 异步消息确保渲染任务优先执行,避免掉帧。
  • 应用层:极少数需要 “突破普通消息队列” 的场景(如紧急日志上报),但需谨慎使用。

HandlerThread

HandlerThread 是 Android 提供的一个带 Looper 的线程类,它将 Thread 与 Handler 结合,简化了 “在子线程中处理消息循环” 的场景。相比手动创建线程并初始化 Looper,HandlerThread 更简洁且线程安全,常用于需要在后台线程持续处理任务的场景(如串口通信、定时任务)。

HandlerThread 核心作用

  • 自带 Looper:内部自动初始化 Looper 和 MessageQueue,无需手动调用 Looper.prepare() 和 Looper.loop()。
  • 消息驱动:通过 Handler 向其发送消息,实现 “主线程向子线程” 或 “子线程间” 的任务调度。
  • 单线程串行执行:所有消息在 HandlerThread 的线程中串行处理,避免多线程并发问题。

基本使用步骤

1. 创建 HandlerThread 实例

指定线程名称(方便调试):

// 创建 HandlerThread,参数为线程名称
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");

2. 启动线程

调用 start() 方法,内部会初始化 Looper:

handlerThread.start(); // 启动线程,触发内部 Looper 初始化

3. 创建子线程 Handler

通过 handlerThread.getLooper() 获取子线程的 Looper,创建绑定到该线程的 Handler:

// 创建子线程 Handler,用于向 HandlerThread 发送消息
Handler workerHandler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 此方法在 HandlerThread 的线程中执行
        switch (msg.what) {
            case 1:
                // 处理任务(如网络请求、数据解析)
                String result = processData((String) msg.obj);
                // 如需更新 UI,可通过主线程 Handler 发送结果
                mainHandler.obtainMessage(1, result).sendToTarget();
                break;
            // 其他任务...
        }
    }
};

4. 发送消息 / 任务

通过 workerHandler 向子线程发送消息或 Runnable:

// 发送消息
Message msg = workerHandler.obtainMessage(1, "任务参数");
workerHandler.sendMessage(msg);

// 或发送 Runnable
workerHandler.post(() -> {
    // 子线程执行的任务
});

5. 销毁线程

不再使用时,需终止 Looper 并释放资源:

// 退出消息循环(必须调用,否则线程不会终止)
handlerThread.quit(); // 或 quitSafely()

核心原理

HandlerThread 的源码非常简洁(约 100 行),核心逻辑在 run() 方法中:

public class HandlerThread extends Thread {
    private Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        // 1. 初始化 Looper(子线程中)
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll(); // 唤醒等待 Looper 初始化的线程
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared(); // 钩子方法,初始化后回调
        // 2. 启动消息循环(阻塞,直到 quit() 被调用)
        Looper.loop();
        mTid = -1;
    }

    // 获取 Looper(若未初始化则阻塞等待)
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // 等待 Looper 初始化完成
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    // 退出消息循环(立即终止未处理的消息)
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    // 安全退出(处理完已入队的消息后终止)
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }
}

核心流程:

  1. start() 触发 run() 方法执行,在子线程中调用 Looper.prepare() 初始化 Looper。
  2. getLooper() 会阻塞等待 Looper 初始化完成,确保获取的 Looper 非空。
  3. Looper.loop() 启动消息循环,线程进入阻塞状态,不断处理 MessageQueue 中的消息。
  4. 调用 quit() 或 quitSafely() 会终止 Looper,消息循环退出,线程结束。

典型应用场景

1. 后台持续任务(如轮询)

需在后台线程定期执行任务(如每隔 5 秒检查网络状态):

// 创建 HandlerThread 用于轮询
HandlerThread pollThread = new HandlerThread("PollThread");
pollThread.start();

// 子线程 Handler
Handler pollHandler = new Handler(pollThread.getLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        if (msg.what == 1) {
            // 执行轮询任务
            checkNetworkStatus();
            // 延迟 5 秒后再次发送消息(循环执行)
            sendEmptyMessageDelayed(1, 5000);
        }
    }
};

// 启动轮询
pollHandler.sendEmptyMessage(1);

2. 子线程间通信

多个子线程通过 HandlerThread 协调任务(避免多线程并发冲突):

// 共享的 HandlerThread 作为"任务调度中心"
HandlerThread schedulerThread = new HandlerThread("Scheduler");
schedulerThread.start();
Handler schedulerHandler = new Handler(schedulerThread.getLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 所有任务在此线程串行执行,无需加锁
        processTask(msg.obj);
    }
};

// 线程 A 发送任务
new Thread(() -> {
    schedulerHandler.obtainMessage(1, "任务A").sendToTarget();
}).start();

// 线程 B 发送任务
new Thread(() -> {
    schedulerHandler.obtainMessage(1, "任务B").sendToTarget();
}).start();

3. 替代 AsyncTask 处理后台任务

在 Java 项目中,可用于简单的后台任务 + 主线程回调:

// 主线程 Handler(用于更新 UI)
Handler mainHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        textView.setText((String) msg.obj);
    }
};

// HandlerThread 处理后台任务
HandlerThread bgThread = new HandlerThread("BgThread");
bgThread.start();
Handler bgHandler = new Handler(bgThread.getLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 后台处理
        String result = heavyCompute((String) msg.obj);
        // 发送结果到主线程
        mainHandler.obtainMessage(1, result).sendToTarget();
    }
};

// 启动任务
bgHandler.obtainMessage(1, "计算参数").sendToTarget();

注意事项

  1. 必须调用 quit () 或 quitSafely ()

    若不调用,HandlerThread 的 Looper 会一直阻塞,导致线程无法销毁,引发内存泄漏。

    • quit():立即终止,未处理的消息会被丢弃。
    • quitSafely():处理完已入队的消息后终止,更安全。
  2. 避免耗时操作阻塞消息循环

    handleMessage() 中的任务若耗时过长,会阻塞后续消息执行(因单线程串行)。复杂任务建议拆分或使用线程池。

  3. 与主线程 Handler 配合使用

    HandlerThread 的 Handler 运行在子线程,若需更新 UI,需通过主线程 Handler 转发结果。

  4. Kotlin 项目的替代方案

    Kotlin 中更推荐使用 协程(Coroutine) 结合 Dispatchers.IO 处理后台任务,语法更简洁,且自带生命周期管理。

总结

HandlerThread 是 “线程 + Looper + Handler” 的封装,核心价值是简化子线程消息循环的实现。适合场景:

  • 后台持续任务(如轮询、监听);
  • 子线程间串行任务调度;
  • 简单的后台计算 + UI 回调。

使用时需注意及时退出 Looper,避免线程泄漏,复杂场景可结合协程或线程池使用。

总结

Handler 是 Android 线程模型的核心,其本质是 “基于消息循环的线程通信工具”,通过 Handler-Looper-MessageQueue-Message 四件套的协同,解决了 “子线程更新 UI” 的核心痛点。

掌握 Handler 需重点理解:

  1. 主线程 Looper 的初始化流程(系统自动完成);
  2. 消息从 “发送→入队→循环→处理” 的完整链路;
  3. 内存泄漏的原因与解决方案;
  4. 与现代 Android 组件(如 Coroutine、LiveData)的关系。

只有深入理解 Handler 原理,才能在面对复杂异步场景(如多线程协作、延迟任务)时写出高效、安全的代码,同时快速排查因消息循环导致的性能问题(如界面卡顿、ANR)。

问题

android Handler避免内存泄露handler.removeCallbacksAndMessages(null)的使用

在Acticity退出时最好调用handler.removeCallbacksAndMessages(null),移除handler的所有消息,避免内存泄漏。记住调用handler.removeCallbacksAndMessages(null)只会移除当前handler的所有消息,如果存在多个handler,需要每一个handler都调用一次。

Android Handler.removeMessage移除所有postDelayed的问题

handler.removeMessage(0)把所有的延时执行可运行任务都移除掉了?

一个新的Message对象,它的what变量默认为0!这就解释了为什么handler.removeMessage(0)会把所有可执行任务都移除掉。

避开removeMessage暗坑方法:

  1. 自定义handler处理的msg.what消息不要使用默认值0
  2. 同一个handler不要同时使用postDelayed()和postMessageDelayed()两个方法

资料

Android 的消息机制
android 利用Handler机制中SyncBarrier的特性实现预加载
Handler 中的 epoll
Android面试(五):异步消息机制之Handler面试你所需知道的一切
Android消息机制(二):Message和MessageQueue
Android 消息机制(Handler Looper Message )理解
详解 Handler 消息处理机制

最近更新:: 2025/10/22 15:36
Contributors: luokaiwen, 罗凯文