当前位置: 首页 > article >正文

Android Handle 机制常见问题深度解析

目录

引言

1. 一个线程可以有几个 Handler?

2. 一个线程可以有几个 Looper?如何保证?

3. Handler 内存泄漏的原因?为什么其他内部类没有这个问题?

4. 为什么主线程可以 new Handler()?子线程中 new Handler 需要做哪些准备?

5. 子线程 Looper 维护的 MessageQueue 在无消息时如何处理?

6. Handler 线程安全性如何保证?

7. 正确创建 Message 的方式?

8. 为什么 Looper 死循环不会导致应用卡死?

9. Handler、Thread、HandlerThread 三者的区别?

10. 自己写个 MessageQueue,可以实现跨线程吗?

11. 如果在子线程写一个MessageQueue 可以让其他子线程调用来实现跨线程吗

11.1 为什么不能直接跨线程操作 MessageQueue?

11.2 如何正确实现跨线程通信?

结论

相关推荐


引言

        在 Android 开发中,Handler 机制是异步消息处理的重要工具,主要用于线程间通信。本文将围绕面试中常见的 Handler 相关问题进行深入解析。

1. 一个线程可以有几个 Handler

一个线程可以拥有 多个 Handler,但它们都必须依赖于同一个 Looper

  • Handler 需要 Looper 进行消息循环,而 Looper 绑定到 ThreadLocal,一个线程通常只有一个 Looper

  • 不同 Handler 共享同一个 MessageQueue,可以发送和处理消息。

Handler handler1 = new Handler(Looper.getMainLooper());
Handler handler2 = new Handler(Looper.getMainLooper());

2. 一个线程可以有几个 Looper?如何保证?

一个线程只能有 一个 Looper,否则会抛出异常。

  • Looper 通过 ThreadLocal 存储在每个线程的本地存储中。

  • 只能调用 Looper.prepare() 一次,否则会抛出异常。

Looper.prepare();  // 初始化 Looper
Looper.prepare();  // 第二次调用会崩溃

3. Handler 内存泄漏的原因?为什么其他内部类没有这个问题?

原因

  • Handler 持有外部类的引用,如果 Handler 被延迟执行,Activity 可能已经销毁,但Handler 仍然持有它,导致内存泄漏

解决方案

  • 使用 静态内部类 + 弱引用 解决。

    class MyHandler internal constructor(activity: Activity) : Handler() {
        private val activityRef = WeakReference(activity)

        override fun handleMessage(msg: Message) {
            val activity = activityRef.get()
            activity?.let {
                // 处理消息(确保在 UI 线程操作 Activity)
            }
        }
    }

注意事项:

  1. 显式指定 Looper:将 Handler() 改为 Handler(Looper.getMainLooper()),明确绑定主线程的 Looper。这符合 Android 新版本的要求,避免使用已弃用的无参构造。

  2. 确保线程安全:即使通过 WeakReference 持有 Activity,仍需确保 handleMessage 中的操作在 UI 线程执行(如更新 UI)。若消息来自子线程,需要通过 activity?.runOnUiThread { ... } 切换线程。

4. 为什么主线程可以 new Handler()?子线程中 new Handler 需要做哪些准备?

主线程可以直接 new Handler() 的原因

  • Android 启动时,ActivityThread.main()已经执行Looper.prepareMainLooper()

  • 主线程默认就有 Looper,所以可以直接 new Handler()

子线程中 new Handler() 的准备工作

  • 必须手动初始化 Looper,否则 Handler 无法工作。

Thread {
    Looper.prepare()
    val handler: Handler = Handler(Looper.myLooper()!!)
    Looper.loop()
}.start()

5. 子线程 Looper 维护的 MessageQueue 在无消息时如何处理?

        当 MessageQueue 为空时,Looper.loop()阻塞(idle)状态,直到新的消息到达。

  • 通过 nativePollOnce() 让线程进入 等待状态,避免 CPU 资源浪费。

  • 当有新消息时,线程会被唤醒。

6. Handler 线程安全性如何保证?

        多个 Handler 可以向 MessageQueue 发送消息,但 MessageQueue 通过 同步锁(synchronized 以及 native 方法 确保线程安全。

  • 入队(enqueueMessage) 使用 synchronized 保证消息添加的安全性。

  • 出队(next) 采用 epoll 机制 监听消息,避免 CPU 轮询浪费。

7. 正确创建 Message 的方式?

        推荐使用 Message.obtain() 进行复用,减少 GC。

Message msg = Message.obtain();
msg.what = 1;
msg.obj = "数据";
handler.sendMessage(msg);

8. 为什么 Looper 死循环不会导致应用卡死?

Looper.loop() 内部是 阻塞等待,而不是无限循环。

while (true) {
    Message msg = queue.next(); // 可能会阻塞
    if (msg == null) continue;
    msg.target.dispatchMessage(msg);
}
  • queue.next() 为空时,线程进入 等待状态,不占用 CPU 资源。

  • 只有当新消息到来时,线程才会被唤醒。

9. HandlerThreadHandlerThread 三者的区别?

名称主要作用是否有 Looper适用场景
Handler处理消息和任务依赖 Looper线程间通信,UI 更新
Thread创建新线程执行任务一次性后台任务
HandlerThread具有 Looper 的线程需要长期运行的后台任务

选择建议

  • 短时间任务:用 Thread + Handler 传递消息。

  • 长期任务:用 HandlerThread,避免频繁创建和销毁线程,提高性能。

10. 自己写个 MessageQueue,可以实现跨线程吗?

        可以。每个线程如果要有消息循环的话,需要调用Looper.prepare(),然后Looper会关联一个MessageQueue。也就是说每个线程有一个MessageQueue。当创建Handle时,把自己写的MessageQueue关联的Looper作为参数传给Handle即可实现跨线程通信。

  1. 每个线程可以有且仅有一个 MessageQueue
    当线程调用 Looper.prepare() 初始化自己的 Looper 时,会创建一个与该线程绑定的 MessageQueue。若线程没有显式创建 Looper,则默认没有 MessageQueue。

  2. 主线程(UI 线程)默认有一个 MessageQueue
    Android 系统会自动为主线程创建 Looper 和 MessageQueue,用于处理 UI 事件和消息。

  3. 其他线程的 MessageQueue 数量由开发者控制
    若开发者主动在其他线程中调用 Looper.prepare() 并启动消息循环(例如通过 HandlerThread),则每个这样的线程会有一个独立的 MessageQueue。

示例:

    // 主线程:默认有 1 个 MessageQueue
    Handler(Looper.getMainLooper()).post {}


    // 子线程 1:显式创建 Looper 和 MessageQueue
    val thread1 = HandlerThread("Thread1")
    thread1.start()
    val handler1 = Handler(thread1.looper)


    // 子线程 2:另一个 Looper 和 MessageQueue
    val thread2 = HandlerThread("Thread2")
    thread2.start()
    val handler2 = Handler(thread2.looper)

        此时进程中存在 3 个 MessageQueue(主线程 + 两个子线程)。

11. 如果在子线程写一个MessageQueue 可以让其他子线程调用来实现跨线程吗

        在 Android 中,不能直接通过让其他子线程操作另一个子线程的 MessageQueue 来实现跨线程通信。这是因为 MessageQueue 是线程私有的资源,设计上并不支持跨线程的直接访问。但可以通过 Handler 机制安全地实现跨线程通信。

11.1 为什么不能直接跨线程操作 MessageQueue

  1. 线程安全性问题
    MessageQueue 内部没有同步机制,如果多个线程同时操作同一个 MessageQueue(例如插入或删除消息),会导致数据竞争和不可预知的行为。

  2. 设计约束
    MessageQueue 是 Looper 的内部实现细节,其生命周期与绑定的线程紧密相关。直接操作 MessageQueue 可能破坏 Android 消息循环的固有逻辑。

11.2 如何正确实现跨线程通信?

        // 目标线程(子线程 B)
        val handlerThread = HandlerThread("WorkerThread")
        handlerThread.start()
        val targetHandler: Handler = object : Handler(handlerThread.looper) {
            override fun handleMessage(msg: Message) {
                // 在子线程 B 处理消息
                Log.d("ThreadB", "Received message: " + msg.what)
            }
        }


        // 发送线程(子线程 A)
        Thread {
            // 发送消息到子线程 B
            targetHandler.sendEmptyMessage(123)
        }.start()

消息处理流程

  1. 发送消息
    线程 A 通过 handler.sendMessage() 将消息插入线程 B 的 MessageQueue

  2. 唤醒目标线程
    若线程 B 的 Looper 处于休眠状态(队列为空),nativeWake() 会唤醒它。

  3. 消息取出与处理
    线程 B 的 Looper 从 MessageQueue 取出消息,并调用 handler.handleMessage() 处理。

结论

  • Handler 依赖 LooperMessageQueue 实现异步消息处理。

  • Looper 在子线程中使用时需手动初始化,并确保调用 Looper.loop() 进入消息循环。

  • MessageQueue 采用 同步锁和 native 方法 确保多线程安全。

  • Looper.loop() 不会导致卡死,因为 MessageQueue 为空时线程会进入阻塞状态。

相关推荐

Android 彻底掌握 Handler 看这里就够了_extends handler-CSDN博客文章浏览阅读1.7k次,点赞16次,收藏19次。Handler 有两个主要用途:1、安排消息和可运行对象在将来的某个时间执行;2、将要在与您自己的线程不同的线程上执行的操作排入队列。_extends handler https://shuaici.blog.csdn.net/article/details/120238927Android Framework 启动流程必知必会-CSDN博客文章浏览阅读1.6k次,点赞3次,收藏4次。Android系统启动涉及Zygote进程,它是通过写时拷贝技术实现资源优化,孵化SystemServer和其他应用进程。SystemServer不直接由init启动,因为Zygote能预先加载资源,避免不必要的服务启动。Zygote孵化应用进程可防止因多线程fork导致的死锁问题。Zygote不使用BinderIPC是为了避免多线程问题,尽管Zygote并非单线程,在fork前后管理线程状态。 https://shuaici.blog.csdn.net/article/details/129348678


http://www.kler.cn/a/590871.html

相关文章:

  • zemax高低温优化
  • 数据库设计实验(3)—— 分离与附加、还原与备份
  • WPS表格导入CSV文件(适合处理数据库导出数据)
  • 深入理解 Qt 系统托盘图标:创建自定义的系统托盘图标类
  • 《C#上位机开发从门外到门内》3-4:基于TCP/IP的远程监控系统设计与实现
  • 基于 Python 爬取 TikTok 搜索数据 Tiktok爬虫(2025.3.17)
  • EMLOG漏洞防护方法(防Webshell、防篡改、防劫持、防SQL注入、防XSS攻击)
  • 分区表和分表
  • 【STM32】uwTick在程序中的作用及用法,并与Delay函数的区别
  • NLP高频面试题(五)——BERT的基本结构介绍、预训练任务、下游任务
  • ubuntu20.04关机进程阻塞解决方法
  • Java+AI:传统编程语言的智能化突围之路
  • 【gopher的java学习笔记】Maven依赖中的scope字段:精准控制依赖生命周期的实战指南
  • 贴吧ip什么意思?贴吧ip可以查到姓名吗
  • 学c++的人可以几天速通python?
  • 杨校老师课堂之编程入门与软件安装【图文笔记】
  • Python 常用内建模块-argparse
  • F8 逐行执行(Step Over) F7 进入方法(Step Into) Shift+F8 跳出方法(Step Out)
  • CAD纤维密堆积3D插件 圆柱体堆积建模
  • 从C语言开始的C++编程生活(1)