Handler面试汇总

122 阅读5分钟

1. Handler消息机制

a. 核心流程

deepseek_mermaid_20250714_ecd548.png

  1. 发送消息(生产者线程)

    • 线程A 调用 handler.sendMessage(msg)post(Runnable)
    • Handler 将消息插入到与当前线程关联的 MessageQueue 中(通过 enqueueMessage())。
  2. 消息队列管理

    • MessageQueue 是一个基于时间优先级的单链表结构,按 when(触发时间)排序。
    • 如果消息是延时的(sendMessageDelayed),会计算出绝对时间再插入队列。
  3. 消息循环(消费者线程)

    • 线程B 的 Looper 通过 loop() 方法无限循环调用 MessageQueue.next()
    • next() 可能会阻塞(如队列为空或队首消息未到执行时间),通过 Linux 的 epoll 机制释放 CPU 资源。
  4. 消息分发与处理

    • Looper 从队列取出消息后,调用 handler.dispatchMessage(msg)

    • Handler 根据消息类型决定执行:

      • 若消息有 Runnable,则执行 Runnable.run()
      • 否则调用 handleMessage(msg)(需子类重写)。

b. 关键组件

(1)Message

  • 消息的载体,包含:

    public int what;       // 消息标识
    public int arg1, arg2; // 简单数据
    public Object obj;     // 复杂对象
    public long when;      // 触发时间(毫秒)
    Handler target;        // 目标Handler
    Runnable callback;     // 回调任务
    
  • 优化建议:通过 Message.obtain()handler.obtainMessage() 复用消息对象(减少内存分配)。

(2)MessageQueue

  • 数据结构:单链表(按 when 排序)。

  • 核心方法

    • enqueueMessage():插入消息并唤醒队列(如果被阻塞)。
    • next():取出消息,可能阻塞(通过 nativePollOnce())。

(3)Looper

  • 作用:驱动消息循环。

  • 关键代码

    public static void loop() {
        for (;;) {
            Message msg = queue.next(); // 可能阻塞
            if (msg == null) return;   // 唯一退出条件
            msg.target.dispatchMessage(msg);
        }
    }
    
  • 线程绑定:通过 ThreadLocal 保证每个线程有独立的 Looper。

(4)Handler

  • 绑定关系:创建时关联当前线程的 Looper。

  • 消息分发逻辑

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            msg.callback.run(); // 处理Runnable
        } else if (mCallback != null) {
            mCallback.handleMessage(msg); // 回调接口
        } else {
            handleMessage(msg); // 子类重写
        }
    }
    

c. 阻塞与唤醒机制

  • 阻塞

    • 队列为空时,next() 调用 nativePollOnce() 进入无限期阻塞。
    • 队首消息未到执行时间,阻塞至指定时间。
  • 唤醒

    • 当新消息插入队列且位于队首时,调用 nativeWake() 唤醒 Looper

d. 同步屏障机制

  1. 设置同步屏障postSyncBarrier()

    • 插入一个特殊token到消息队列
    • 会阻塞所有同步消息的处理
  2. 异步消息

    • 设置Message.setAsynchronous(true)
    • 不受同步屏障影响
  3. 移除同步屏障removeSyncBarrier(token)

  4. 应用场景:ViewRootImpl在绘制UI时使用同步屏障确保绘制消息优先处理

e. IDLEHandler机制

IdleHandler允许在消息队列空闲时执行任务:

  // 消息队列为空时,才会获取idleHandler的数量
  if (pendingIdleHandlerCount < 0
    && (mMessages == null || now < mMessages.when)) {
      pendingIdleHandlerCount = mIdleHandlers.size();
  }
  // 只有在消息队列为空,且没有idlehandler时,才会阻塞,否则会执行idlehandler
  if (pendingIdleHandlerCount <= 0) {
      // No idle handlers to run.  Loop and wait some more.
      mBlocked = true;
      continue;
  }

特点:

  • 当消息队列没有立即要处理的消息时触发
  • 可用于执行低优先级的后台任务

2. 常见面试题

a. Handler 引起的内存泄露原因以及最佳解决方案

  • 原因:延迟消息,持有了强引用如activity,如果activity销毁了,就会泄漏activity
  • 解决方案:持有弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。

b. Handler 里藏着的 Callback 能干什么

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg); // msg自带的callback优先级最高,对应handler.post中的runnable
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) { // 拦截消息
                return;
            }
        }
        handleMessage(msg);
    }
}

c. Handler中post和send的区别

  • post底层也是调用的sendMessageDelayed

  • post是send自带runnable的send:

    • 省去了创建消息的过程
    • 可以快速执行消息,且无法被handler拦截

d. 创建 Message 实例的最佳方式

为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗:

  • 通过 Message 的静态方法 Message.obtain();
  • 通过 Handler 的公有方法 handler.obtainMessage()

e. 为何不使用 Java 中的 wait/notify 来实现阻塞等待呢?

1. 设计目标不同

  • wait()/notify()

    • 是 Java 原生的 线程间低级同步机制,主要用于 锁竞争条件下的线程阻塞/唤醒
    • 需要手动管理 synchronized 块和锁对象,容易出错(如死锁、虚假唤醒)。
  • Handler

    • 是 Android 的 消息驱动机制,专为 线程间通信 设计,尤其适合 主线程与子线程的交互
    • 内部封装了 MessageQueueLooper,自动处理消息排队、分发和线程切换。

2. Handler 的优势

(1) 避免死锁风险

  • wait()/notify() 必须配合 synchronized 使用,若锁未正确释放或唤醒顺序错误,容易导致死锁。
  • Handler 通过 无锁队列MessageQueue)实现消息传递,无需显式同步。

(2) 支持延时和异步

  • Handler 可以轻松发送 延时消息postDelayed())或 异步任务post()),而 wait() 只能被动等待唤醒。

(3) 天然适配主线程(UI线程)

  • Android 的主线程默认有一个 Looper,通过 Handler 可以安全地更新 UI(子线程通过 Handler 将任务抛到主线程执行)。
  • wait()/notify() 若在主线程误用,会导致 ANR(主线程阻塞)。

(4) 更灵活的消息处理

  • Handler 可以区分不同类型的消息(通过 msg.what),并统一在 handleMessage() 中处理。
  • wait()/notify() 只能通过全局状态变量判断,代码难以维护。

f. View.post 和 Handler.post的区别

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action); // 1. 本质上仍然调用handler.post
    }
​
    // 2. 区别是如果View还没有attach,则会先存起来
    getRunQueue().post(action);
    return true;
}
​
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        // 3. attach的时候,执行这些暂存的action
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
    }
​