2.2026金三银四 Android Handler 完全指南:28道高频面试题 + 源码解析 + 图解 (一文通关)

27 阅读4分钟

Q1. Handler为什么可以切换线程?

答案: Handler实现线程切换的关键在于:不同线程的Handler与特定的Looper绑定,而Looper运行在其绑定的线程中。

图解:

graph TD
    subgraph 子线程
        A[子线程任务] -->|发送消息| B(Handler实例)
    end
    B -->|消息入队| C(主线程MessageQueue)
    C -->|Looper.loop循环| D{主线程Looper}
    D -->|取出消息| E[Handler.dispatchMessage]
    E -->|回调| F[handleMessage 在主线程执行]

源码解析:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    msg.target = this;  // 绑定当前Handler
    return queue.enqueueMessage(msg, uptimeMillis);
}

Q2. 为什么不能在子线程直接更新 UI?

答案:

  1. UI控件非线程安全,多线程并发操作会导致崩溃
  2. 加锁会降低UI访问效率
  3. Android采用单线程UI模型

源码解析:

// ViewRootImpl.java
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(...);
    }
}

Q3. 请描述 Handler、Looper、MessageQueue、Message 四者的关系。

答案: Handler发送Message到MessageQueue,Looper循环取出Message并派发给Handler处理。

图解:

graph TD
    A[Handler] -->|sendMessage| B[MessageQueue]
    B -->|存储| C[Message]
    D[Looper] -->|loop轮询| B
    D -->|取出| E[msg.target]
    E -->|dispatchMessage| A

数量关系: 1个线程 : 1个Looper : 1个MessageQueue : N个Handler


Q4. Looper 是怎么工作的?主线程和子线程 Looper 区别?

答案:

  • 主线程:ActivityThread.main()中自动调用prepareMainLooper()和loop()
  • 子线程:需要手动调用Looper.prepare()和Looper.loop()

图解:

graph TD
    subgraph 主线程自动创建
        A[ActivityThread.main] --> B[Looper.prepareMainLooper]
        B --> C[Looper.loop]
    end
    subgraph 子线程手动创建
        E[new Thread] --> F[Looper.prepare]
        F --> G[new Handler]
        G --> H[Looper.loop]
    end

Q5. Looper.loop() 是死循环,为什么不会卡死主线程?

答案: 无消息时nativePollOnce()阻塞,线程休眠释放CPU;有消息时nativeWake()唤醒。

图解:

graph TD
    A[Looper.loop] --> B{有消息?}
    B -->|无| C[nativePollOnce阻塞]
    C --> D[线程休眠释放CPU]
    D -->|新消息| E[nativeWake唤醒]
    E --> B
    B -->|有| F[处理消息]
    F --> B

源码解析:

Message next() {
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);  // 阻塞等待
    }
}

Q6. Message 如何创建更好?为什么?

答案: 使用Message.obtain()handler.obtainMessage(),从对象池复用,避免频繁GC。

图解:

graph TD
    A[需要Message] --> B{Message.obtain}
    B --> C{对象池有空闲?}
    C -->|有| D[从池中复用]
    C -->|无| E[new 新对象]
    F[使用完毕] --> G[recycleUnchecked]
    G --> H{池大小<50?}
    H -->|是| I[放回池中]
    H -->|否| J[等待GC]

Q7. 消息是怎么按时间排序执行的?

答案: MessageQueue是单向链表,按when升序排列。延时消息插入到合适位置,Looper取队首,时间未到则阻塞。

图解:

graph TD
    subgraph 队列结构
        Head --> A[when=10] --> B[when=30] --> C[when=50] --> Null
    end
    subgraph 插入when=20
        New[when=20] --> X{查找位置}
        X --> Insert[插入A和B之间]
    end

Q8. 一个线程可以有几个 Handler、Looper、MessageQueue?

答案:

  • Looper:1个/线程(ThreadLocal保证)
  • MessageQueue:1个/线程
  • Handler:多个

图解:

graph TD
    subgraph 单个线程
        L[Looper 唯一] --> Q[MessageQueue 唯一]
        Q --> H1[Handler1] & H2[Handler2] & H3[HandlerN]
    end

Q9. quit() 和 quitSafely() 区别?

答案:

  • quit():立即退出,丢弃所有未处理消息
  • quitSafely():安全退出,处理完当前已分发消息后退出

图解:

graph TD
    A[退出Looper] --> B{quit还是quitSafely?}
    B -->|quit| C[立即退出]
    C --> D[丢弃所有消息]
    B -->|quitSafely| E[执行完当前消息]
    E --> F[队列变空后退出]

Q10. 主线程怎么切到子线程?

答案: 在子线程中先prepare Looper,创建Handler,然后loop;主线程调用该Handler发送消息。

图解:

graph TD
    subgraph 子线程
        A[Looper.prepare] --> B[创建Handler] --> C[Looper.loop]
    end
    subgraph 主线程
        D[调用子线程Handler] --> E[发送Message]
    end
    E --> C

Q11. Handler 为什么会导致内存泄漏?

答案: 非静态内部类Handler隐式持有Activity引用,延迟消息未处理时Message持有Handler,导致Activity无法被GC。

图解:

graph TD
    Activity -->|持有| Handler[非静态Handler]
    Handler -->|持有| Message
    Message -->|持有| Queue[MessageQueue]
    style Activity fill:#ff9999

Q12. 如何解决 Handler 内存泄漏?

答案:

  1. 静态内部类 + WeakReference
  2. onDestroy中调用removeCallbacksAndMessages(null)

源码解析:

static class MyHandler extends Handler {
    private final WeakReference<MainActivity> mActivity;
    // ...
}
@Override
protected void onDestroy() {
    handler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

Q13. 为什么主线程不会因为 Handler 泄漏?

答案: 主线程Looper生命周期和应用进程一致,始终存在,不存在回收问题。


Q14. sendMessage 和 post(Runnable) 区别?

答案: 本质无区别,post内部将Runnable封装成Message(callback=Runnable),最终都调用sendMessageAtTime。

源码解析:

public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        msg.callback.run();  // post方式
    } else {
        handleMessage(msg);  // sendMessage方式
    }
}

Q15. 子线程中可以创建 Handler 吗?需要注意什么?

答案: 可以,需要先调用Looper.prepare(),再创建Handler,最后调用Looper.loop()。

图解:

graph TD
    A[子线程创建Handler] --> B{调用prepare?}
    B -->|否| C[崩溃]
    B -->|是| D[创建成功]
    D --> E{调用loop?}
    E -->|否| F[消息无法处理]
    E -->|是| G[正常工作]

Q16. Handler 与 AsyncTask、RxJava、协程的区别?

答案:

graph TD
    A[Handler] --> A1[底层消息机制/代码繁琐]
    B[AsyncTask] --> B1[已废弃]
    C[RxJava] --> C1[响应式编程/包体积大]
    D[Coroutine] --> D1[官方推荐/轻量]

Q17. IdleHandler 是什么?有什么用?

答案: MessageQueue空闲时回调的接口。用于启动优化(延迟初始化SDK)、空闲时释放内存等。

源码解析:

Looper.myQueue().addIdleHandler(new IdleHandler() {
    @Override
    public boolean queueIdle() {
        initThirdPartySDK();
        return false;
    }
});

Q18. 什么是同步屏障?什么是异步消息?

答案: 同步屏障是target=null的特殊消息,插入后Looper跳过所有同步消息,只处理异步消息。用于保证UI绘制优先执行。

图解:

graph TD
    A[MessageQueue] --> B{有同步屏障?}
    B -->|有| C[跳过同步消息]
    C --> D[只处理异步消息]
    D --> E[UI绘制优先]

Q19. HandlerThread 是什么?与普通 Thread 的区别?

答案: HandlerThread是自带Looper的子线程,内部封装了prepare和loop。普通Thread执行完run就销毁,HandlerThread可以持续接收消息。

图解:

graph TD
    subgraph 普通Thread
        T1[启动] --> T2[执行run] --> T3[销毁]
    end
    subgraph HandlerThread
        H1[启动] --> H2[Looper.prepare] --> H3[Looper.loop]
        H3 --> H4[等待消息/不销毁]
    end

Q20. IntentService 与 HandlerThread 的关系?

答案: IntentService内部使用HandlerThread,通过ServiceHandler串行处理Intent,处理完自动销毁。


Q21. 多个 Handler 往同一个线程发送消息,Message 是如何区分属于哪个 Handler 的?

答案: 通过Message的target字段区分,它指向发送该消息的Handler对象。

图解:

graph TD
    H1[Handler A] -->|target=A| M1[Message A]
    H2[Handler B] -->|target=B| M2[Message B]
    M1 --> Q[同一队列]
    M2 --> Q
    Q --> L[Looper取出]
    L -->|target=A| H1
    L -->|target=B| H2

Q22. 主线程的 Looper 在哪个类里被初始化的?

答案: 在ActivityThread的main()方法中。

源码解析:

public static void main(String[] args) {
    Looper.prepareMainLooper();
    Looper.loop();
}

Q23. 线程和 Looper 是如何保证一对一关系的?

答案: 通过ThreadLocal实现,每个线程存储自己的Looper副本。

源码解析:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

Q24. Looper.loop() 是死循环,它后面的代码什么时候执行?

答案: 只有调用quit()退出循环后才会执行,正常情况下永远不会执行。

图解:

graph TD
    A[调用loop] --> B[进入死循环]
    B --> C{quit?}
    C -->|否| B
    C -->|是| D[退出循环]
    D --> E[执行后续代码]

Q25. View.post(Runnable) 为什么能拿到 View 的宽高?

答案: View.post将Runnable发送到主线程Handler,且排在绘制流程之后执行,此时View已完成测量和布局。

图解:

graph TD
    A[View.post] --> B{已attach?}
    B -->|是| C[主线程Handler]
    B -->|否| D[暂存RunQueue]
    D --> E[attach后执行]
    C --> F[排在绘制之后]
    F --> G[能获取宽高]

Q26. 完整描述 Handler 消息分发流程

图解:

graph TD
    subgraph 发送
        H[Handler] --> M[Message] --> Q[MessageQueue]
    end
    subgraph 循环
        L[Looper.loop] --> N[queue.next] --> Get[取出Message]
    end
    subgraph 分发
        Get --> D[msg.target.dispatchMessage]
        D -->|callback存在| R[执行Runnable]
        D -->|其他| HM[handleMessage]
    end

Q27. Handler 可能引起的问题

答案:

graph TD
    A[Handler问题] --> B[内存泄漏]
    A --> C[消息延迟UI错乱]
    A --> D[子线程未prepare崩溃]
    A --> E[耗时操作导致ANR]
    A --> F[quit导致消息丢失]

Q28. Handler 发送延迟消息(postDelayed)的原理是什么?

答案: 计算执行时间when=当前时间+delayMillis,按when插入队列,Looper取队首时若时间未到则nativePollOnce阻塞。

图解:

graph TD
    A[postDelayed] --> B[when = now + delay]
    B --> C[按when插入队列]
    C --> D[Looper取队首]
    D --> E{时间到了?}
    E -->|否| F[nativePollOnce阻塞]
    F --> E
    E -->|是| G[执行]

面试官期望的加分项

graph TD
    A[加分项] --> B[epoll模型]
    A --> C[消息池机制]
    A --> D[ThreadLocal]
    A --> E[同步屏障]
    A --> F[IdleHandler]