1. Native 侧的 NativeMessageQueue 和 Looper 的作用是什么?
- NativeMessageQueue:Native 层的消息队列,负责接收和分发 Native 层的 Message(如 Input、Vsync 信号)。它与 Java 层 MessageQueue 通过 JNI 关联,实现跨层消息通信。
- Native Looper:Native 层的循环器,负责线程阻塞等待 + 精准唤醒 + 底层 fd(File Descriptor 文件描述符,如管道、输入设备)事件监听。基于 epoll 实现,配合 Java 层的 Looper。
- Java 层 MessageQueue/Looper 是无阻塞/唤醒能力的,依靠 Native 层 MessageQueue/Looper 实现线程阻塞/唤醒。
2. Native 侧如何使用 Looper?
- 步骤 :
- 初始化 NativeLooper 和 NativeMessageQueue;
- 底层依赖 epoll 实现 IO 多路复用阻塞;
- 消息无任务时:阻塞线程、挂起;
- 插入新消息:写入消息,唤醒阻塞线程
- Java 层的
loop()死循环,最终依赖 Native 层的阻塞唤醒机制;
- 典型应用:Input 系统、Sensor 服务、SurfaceFlinger。
3. HandlerThread 是什么,有哪些使用场景和用法?
-
定义: 是 Thread 的子类,内部自动创建了 Looper + MessageQueue;
-
使用场景:需要子线程处理异步任务且需要消息队列(如后台网络请求、数据库操作串行化);
-
销毁: 必须调用
quit/quitSafely防止内存泄漏; -
用法:
javaHandlerThread thread = new HandlerThread("MyThread"); thread.start(); Handler handler = new Handler(thread.getLooper()); handler.post(runnable);
4. IdleHandler 空闲 Message 了解过吗?有什么用
- 定义:MessageQueue.IdleHandler(MessageQueue 的内部静态接口),当消息队列空闲,暂无任务执行时被调用;
- 作用:在空闲时执行低优先级任务,如预加载、延迟初始化、埋点、统计、GC 检查等,避免阻塞主线程。
- 用法 :
Looper.myQueue().addIdleHandler(IdleHandler),返回true表示保留,持续监听,false表示执行后移除。
5. 如何理解 ThreadLocal 的作用
- 作用:为每个线程提供独立的变量副本,线程间隔离,互不干扰。
- 原理 :每个线程内部维护
ThreadLocalMap,以 ThreadLocal 为弱引用键,存储副本值。 - 典型应用:Looper 存储(每个线程唯一的 Looper)、SimpleDateFormat 线程安全包装。
6. ThreadLocal 在 Android 中的应用场景有哪些?它的实现原理是什么?
- 应用场景 :
Looper存储(每个线程唯一 Looper)。Handler构造时获取当前线程 Looper。- 性能监控数据收集(每个线程独立耗时记录)。
- 避免参数传递的上下文对象(如用户身份、事务管理器)。
- 实现原理 :
- 每个
Thread内部维护一个ThreadLocalMap。 ThreadLocal作为键(弱引用),存储副本值到当前线程的 Map 中。set/get操作都是操作当前线程的 Map,实现线程隔离。- 注意内存泄漏:
ThreadLocalMap的 key 为弱引用,但 value 为强引用,用完后需remove()。
- 每个
7. Looper 存在哪?如何能保证线程独有
- 存储位置 :Looper 实例存储在当前线程(Thread)的 ThreadLocalMap 中,而
ThreadLocal<Looper>作为键值,是一个访问入口,用于存取; - 详细解释:
- 每个 Thread 内部维护着一个 ThreadLocalMap 类型的成员变量是
threadLocals,这个 Map 的键值是 ThreadLocal 对象(弱引用),值是线程独有的变量副本; - 在 Looper 类中,定义了一个静态的
ThreadLocal<Looper>对象sThreadLocal,当调用Looper.prepare()时会执行sThreadLocal.set(new Looper()),这就是将 Looper 实例存储到当前线程的 ThreadLocalMap 中,键是sThreadLocal本身;
- 每个 Thread 内部维护着一个 ThreadLocalMap 类型的成员变量是
- 保证唯一性 :
Looper.prepare()检查当前线程是否已有 Looper,若有则抛异常;prepare()只能调用一次,loop()开始循环。因此每个线程最多一个 Looper。
8. Looper 的等待是如何能够准确唤醒的?
- 底层机制 :MessageQueue 的
next()方法调用nativePollOnce(ptr, timeoutMillis),进入 epoll_wait 阻塞。 - 唤醒时机 :
- 有新消息入队时(
enqueueMessage调用nativeWake写管道)。 - 超时时间到。
- 外部主动调用
quit()。
- 有新消息入队时(
- 原理:基于 Linux epoll + 管道,向管道写一个字节触发 epoll 返回。
9. Looper.loop() 死循环为什么没有卡死/阻塞主线程,原理是什么?
- 不会卡死 :因为主线程的 Looper 会处理各种事件(UI 绘制、输入、定时器等),每次循环处理一个消息后短暂返回,等待下一次唤醒。没有消息时阻塞在
nativePollOnce,不消耗 CPU; - 原理:Java 层死循环只是逻辑上的"无限",实际底层通过 epoll 阻塞让出 CPU,有事件时才被唤醒。同时,主线程的所有操作(如点击事件、动画)都是通过 Looper 派发的,循环保证了这些操作得以执行;
- 看似是死循环,但是大部分时间是在休眠,只有在有任务是才执行;
10. 主线程 Main Looper 和一般的 Looper 的异同
| 对比 | Main Looper | 普通 Looper |
|---|---|---|
| 创建 | 由系统在 ActivityThread 中自动调用 prepareMainLooper() |
开发者调用 prepare() |
| 退出 | 不允许退出(调用 quit 会抛异常) | 可以退出 |
| 优先级 | 较高,处理 UI 事件 | 普通 |
| 相同点 | 都是 Looper,使用相同的消息队列机制 |
11. Handler 或者 Looper 如何切换线程
- 原理 :Handler 绑定到特定线程的 Looper,
sendMessage会将消息放入该 Looper 的消息队列,最终由 Looper 所在线程处理。 - 切换方式:在目标线程中创建 Handler,其他线程通过该 Handler 发送消息,即可将任务切换到目标线程执行。
- 示例:子线程处理耗时任务后,通过主线程 Handler 更新 UI。
12. 为什么在子线程中创建 Handler 会抛出异常?
- 原因 :Handler 的构造方法会调用
Looper.myLooper()获取当前线程的 Looper。若为 null(子线程默认没有 Looper),则抛出RuntimeException: Can't create handler inside thread that has not called Looper.prepare()。 - 解决 :在子线程中先调用
Looper.prepare()创建 Looper,再 new Handler,最后调用Looper.loop()。
13. 异步 Message 或同步屏障了解过吗?怎么用?什么原理?
- 同步屏障 :通过
MessageQueue.postSyncBarrier()插入一个 target 为 null 的 Message,从此之后只处理异步消息,普通消息被阻塞。移除屏障用removeSyncBarrier()。 - 异步 Message :
Handler构造时传入async=true或调用setAsynchronous(true),该 Handler 发送的消息即为异步消息。 - 使用场景 :系统 UI 绘制(
ViewRootImpl的TraversalRunnable)使用同步屏障优先处理异步消息。 - 原理 :
MessageQueue.next()遇到同步屏障时,跳过所有同步消息,只查找异步消息。
14. 试从源码角度分析 Handler.post 和 sendMessage 方法的区别和应用场景
- 源码分析 :
post(Runnable r):内部调用sendMessageDelayed(getPostMessage(r), 0),将 Runnable 包装成 Message(callback 为 Runnable)。sendMessage(Message msg):直接发送 Message。- 两者最终都调用
enqueueMessage入队,处理时dispatchMessage优先执行 Message.callback(Runnable),其次才是 Handler.handleMessage。
- 区别 :
post方便执行 Runnable 任务,无需自定义 Message;sendMessage适合复用 Message 对象或需要携带数据(obj/arg)。 - 应用场景 :
post常用于简单异步更新 UI;sendMessage用于复杂数据传递。
15. Handler、Message 和 Runnable 的关系如何理解?
- Handler:消息的发送者和处理器,负责发送 Message/Runnable 到消息队列,并在目标线程处理。
- Message:消息载体,携带数据(what、arg、obj)和回调(callback)。
- Runnable :通过
Handler.post(runnable)被包装成 Message(callback 为 Runnable),执行时直接调用 run()。 - 关系 :Handler 发送 Message(或 Runnable 转换的 Message)到 MessageQueue,Looper 取出后交由 Handler 的
dispatchMessage处理,最终执行handleMessage或 Runnable。
16. Message 的执行时刻如何管理?
- 管理机制 :Message 包含
when字段(绝对时间戳,系统开机时间)。MessageQueue 按when升序排列,next()取出队首消息,若当前时间 <when,则计算超时时间,调用nativePollOnce阻塞直到超时或被唤醒。 - 关键 :
enqueueMessage插入时排序,next按时间阻塞,实现延迟/定时执行。
17. Message 如何获取?为什么这么设计?
- 获取方式 :推荐使用
Message.obtain()或Handler.obtainMessage(),而不是直接new Message()。 - 原因 :内部维护一个消息池 (单链表复用),
obtain优先从池中取空闲 Message,避免频繁创建和销毁对象,减少 GC 压力,提升性能。 - 设计:享元模式 + 对象池。
18. MessageQueue 如何管理 Message?
- 数据结构 :单链表按
when时间排序。 - 核心方法 :
enqueueMessage:插入消息,插入后若消息在头部则调用nativeWake唤醒阻塞的 Looper。next:取出下一条消息,若无消息或未到时间则阻塞(nativePollOnce),支持同步屏障(只取异步消息)。
- 管理:支持插入、删除、唤醒、阻塞。
19. 理解 Message 和 MessageQueue 的异同
| 对比 | Message | MessageQueue |
|---|---|---|
| 角色 | 消息实体 | 消息容器 |
| 数据结构 | 单个节点(含 next 指针) | 链表(Message 头指针) |
| 存储 | 携带数据和回调 | 管理消息顺序和生命周期 |
| 相同点 | 都属于 android.os 包,共同构成消息机制基础 |
20. Handler 为什么可能导致内存泄漏?如何避免?
- 原因:非静态内部类 Handler 持有外部 Activity/Fragment 的隐式引用;若 Handler 有延迟消息未处理,则 Activity 无法被 GC 回收。
- 避免方法 :
- 使用静态内部类 + 弱引用(
WeakReference<Activity>)。 - 在
onDestroy/onStop中调用handler.removeCallbacksAndMessages(null)移除所有消息。
- 使用静态内部类 + 弱引用(
21. 谈谈 Handler 机制的原理,在系统中的应用
- 原理 :Handler 发送 Message 到 MessageQueue,Looper 循环取消息并回调 Handler 的
dispatchMessage,实现线程间通信。 - 系统应用 :
- 主线程 Looper 驱动 UI 绘制、事件分发(Input、Activity 生命周期)。
AsyncTask内部使用 Handler 切换线程。IntentService使用 HandlerThread 串行处理。ViewRootImpl通过 Handler 调度绘制。
22. Looper 和 MessageQueue、Message 及 Handler 的关系
- Looper:循环器,每个线程一个,负责不断从 MessageQueue 取消息并分发给 Handler。
- MessageQueue:消息队列,Looper 持有它,管理消息存储和排序。
- Message:消息体,包含数据、时间戳、目标 Handler(target 字段)。
- Handler:发送和最终处理消息,持有 Looper 和 MessageQueue 引用。
- 关系链:Handler 通过 Looper 获取 MessageQueue,入队 Message;Looper 循环取 Message,调用 Message.target.dispatchMessage。
23. Android 为什么不允许并发访问 UI/子线程更新 UI?
- 原因:UI 控件非线程安全,多线程并发更新会导致界面错乱、死锁甚至崩溃。若加锁会降低 UI 性能。
- 设计 :单线程模型(UI 线程),所有 UI 操作必须在主线程执行。通过
ViewRootImpl.checkThread()检测并抛异常。 - 例外 :
SurfaceView可在子线程绘制。
24. 谈谈你对 Activity.runOnUiThread 的理解
- 作用:在任意线程中调用,将 Runnable 切换到主线程执行,用于更新 UI。
- 原理 :内部判断当前线程是否为主线程,若是则直接执行;否则通过主线程的 Handler 发送消息(
mHandler.post(runnable))。系统封装好的主线程 Handler,方便切换线程。 - 等效 :
new Handler(Looper.getMainLooper()).post(runnable)。
25. IntentService 的应用场景和使用姿势
- 定义: 继承自 Service,内部实现 HandlerThread(自带 Looper + MessageQueue 的线程/Thread)
- 场景:需要异步处理任务且任务应串行执行(如后台下载、上传),处理完成后自动停止。
- 使用姿势 :
- 继承 IntentService,实现
onHandleIntent(Intent intent)。 - 在 AndroidManifest 中注册 Service。
- 通过
startService启动,任务执行完后自动调用stopSelf销毁。
- 继承 IntentService,实现
- 注意 :IntentService 已废弃,推荐用
JobIntentService或WorkManager。
26. IntentService 和 Service 有什么区别,使用场景分别是什么?
| 对比 | IntentService | Service |
|---|---|---|
| 线程 | 自带工作线程(HandlerThread),串行处理 | 默认运行在主线程,需手动创建子线程 |
| 生命周期 | 处理完所有 Intent 后自动停止 | 需手动 stopSelf 或 stopService |
| 代码复杂度 | 简单,只需实现 onHandleIntent |
需管理线程和停止逻辑 |
| 多个请求 | 串行执行,依次处理 | 可并行,需自行控制 |
| 适用场景 | 后台连续执行多个任务(如批量日志上传) | 需要长期后台运行或同时处理多个请求 |
- 注意 :IntentService 在 API 30 被标记为废弃,推荐改用
JobIntentService或WorkManager。
27. AsyncTask 的优点和缺点
| 优点 | 缺点 |
|---|---|
| 轻量级,简化异步任务和 UI 更新 | 内部 Handler 和线程池可能导致内存泄漏 |
| 提供回调(onPreExecute, doInBackground, onProgressUpdate, onPostExecute) | 生命周期与 Activity 不同步,易引发崩溃 |
| 支持进度更新 | 串行执行(默认),并行需手动配置 |
已废弃(API 30+),官方推荐用 Kotlin 协程或 LiveData |