Android 异步任务和消息机制

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?

  • 步骤
    1. 初始化 NativeLooper 和 NativeMessageQueue;
    2. 底层依赖 epoll 实现 IO 多路复用阻塞;
    3. 消息无任务时:阻塞线程、挂起;
    4. 插入新消息:写入消息,唤醒阻塞线程
    5. Java 层的 loop() 死循环,最终依赖 Native 层的阻塞唤醒机制;
  • 典型应用:Input 系统、Sensor 服务、SurfaceFlinger。

3. HandlerThread 是什么,有哪些使用场景和用法?

  • 定义: 是 Thread 的子类,内部自动创建了 Looper + MessageQueue;

  • 使用场景:需要子线程处理异步任务且需要消息队列(如后台网络请求、数据库操作串行化);

  • 销毁: 必须调用 quit/quitSafely 防止内存泄漏;

  • 用法

    java 复制代码
    HandlerThread 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 本身;
  • 保证唯一性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()
  • 异步 MessageHandler 构造时传入 async=true 或调用 setAsynchronous(true),该 Handler 发送的消息即为异步消息。
  • 使用场景 :系统 UI 绘制(ViewRootImplTraversalRunnable)使用同步屏障优先处理异步消息。
  • 原理MessageQueue.next() 遇到同步屏障时,跳过所有同步消息,只查找异步消息。

14. 试从源码角度分析 Handler.postsendMessage 方法的区别和应用场景

  • 源码分析
    • 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 回收。
  • 避免方法
    1. 使用静态内部类 + 弱引用(WeakReference<Activity>)。
    2. 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)
  • 场景:需要异步处理任务且任务应串行执行(如后台下载、上传),处理完成后自动停止。
  • 使用姿势
    1. 继承 IntentService,实现 onHandleIntent(Intent intent)
    2. 在 AndroidManifest 中注册 Service。
    3. 通过 startService 启动,任务执行完后自动调用 stopSelf 销毁。
  • 注意 :IntentService 已废弃,推荐用 JobIntentServiceWorkManager

26. IntentService 和 Service 有什么区别,使用场景分别是什么?

对比 IntentService Service
线程 自带工作线程(HandlerThread),串行处理 默认运行在主线程,需手动创建子线程
生命周期 处理完所有 Intent 后自动停止 需手动 stopSelfstopService
代码复杂度 简单,只需实现 onHandleIntent 需管理线程和停止逻辑
多个请求 串行执行,依次处理 可并行,需自行控制
适用场景 后台连续执行多个任务(如批量日志上传) 需要长期后台运行或同时处理多个请求
  • 注意 :IntentService 在 API 30 被标记为废弃,推荐改用 JobIntentServiceWorkManager

27. AsyncTask 的优点和缺点

优点 缺点
轻量级,简化异步任务和 UI 更新 内部 Handler 和线程池可能导致内存泄漏
提供回调(onPreExecute, doInBackground, onProgressUpdate, onPostExecute) 生命周期与 Activity 不同步,易引发崩溃
支持进度更新 串行执行(默认),并行需手动配置
已废弃(API 30+),官方推荐用 Kotlin 协程或 LiveData
相关推荐
被开发耽误的大厨2 小时前
5、Integer缓存池里同一个对象指的是什么?Integer 和String 内存结构逻辑完全一样?
android·java·哈希算法
NoSi EFUL10 小时前
MySQL中ON DUPLICATE KEY UPDATE的介绍与使用、批量更新、存在即更新不存在则插入
android·数据库·mysql
安小牛12 小时前
Android 开发汉字转带声调的拼音
android·java·学习·android studio
聚美智数13 小时前
企业实际控制人查询-公司实控人查询
android·java·javascript
JMchen12314 小时前
第 3 篇|Android 项目结构解析与第一个界面 —— Hello, CSDN!
android·android studio·android 零基础·android 项目结构·android 界面开发
众少成多积小致巨17 小时前
Soong构建入门
android·go·编译器
笔夏17 小时前
【安卓学习之混淆】记录一些混淆导致闪退
android·学习
阿巴斯甜17 小时前
Kotlin高阶函数和Java 8 lambda的区别:
android
张小潇17 小时前
AOSP15 WMS/AMS系统开发 - WindowManagerService relayout调用流程详解
android