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)
}
}
} 注意事项:
[*] 显式指定 Looper:将 Handler() 改为 Handler(Looper.getMainLooper()),明确绑定主线程的 Looper。这符合 Android 新版本的要求,避免使用已弃用的无参构造。
[*] 确保线程安全:纵然通过 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. Handler、Thread、HandlerThread 三者的区别?
名称主要作用是否有 Looper适用场景Handler处理消息和任务依赖 Looper线程间通讯,UI 更新Thread创建新线程实行任务无一次性背景任务HandlerThread具有 Looper 的线程有需要长期运行的背景任务 选择发起:
[*] 短时间任务:用 Thread + Handler 传递消息。
[*] 长期任务:用 HandlerThread,避免频繁创建和销毁线程,提高性能。
10. 自己写个 MessageQueue,可以实现跨线程吗?
可以。每个线程如果要有消息循环的话,需要调用Looper.prepare(),然后Looper会关联一个MessageQueue。也就是说每个线程有一个MessageQueue。当创建Handle时,把自己写的MessageQueue关联的Looper作为参数传给Handle即可实现跨线程通讯。
[*] 每个线程可以有且仅有一个 MessageQueue:
当线程调用 Looper.prepare() 初始化自己的 Looper 时,会创建一个与该线程绑定的 MessageQueue。若线程没有显式创建 Looper,则默认没有 MessageQueue。
[*] 主线程(UI 线程)默认有一个 MessageQueue:
Android 系统会自动为主线程创建 Looper 和 MessageQueue,用于处理 UI 事件和消息。
[*] 其他线程的 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?
[*] 线程安全性问题
MessageQueue 内部没有同步机制,如果多个线程同时操纵同一个 MessageQueue(比方插入或删除消息),会导致数据竞争和不可预知的行为。
[*] 设计束缚
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() 消息处理流程
[*] 发送消息:
线程 A 通过 handler.sendMessage() 将消息插入线程 B 的 MessageQueue。
[*] 唤醒目的线程:
若线程 B 的 Looper 处于休眠状态(队列为空),nativeWake() 会唤醒它。
[*] 消息取出与处理:
线程 B 的 Looper 从 MessageQueue 取出消息,并调用 handler.handleMessage() 处理。
结论
[*] Handler 依赖 Looper 和 MessageQueue 实现异步消息处理。
[*] Looper 在子线程中使用时需手动初始化,并确保调用 Looper.loop() 进入消息循环。
[*] MessageQueue 采用 同步锁和 native 方法 确保多线程安全。
[*] Looper.loop() 不会导致卡死,由于 MessageQueue 为空时线程会进入壅闭状态。
相关推荐
Android 彻底把握 Handler 看这里就够了_extends handler-CSDN博客文章浏览阅读1.7k次,点赞16次,收藏19次。Handler 有两个主要用途:1、安排消息和可运行对象在将来的某个时间实行;2、将要在与您自己的线程不同的线程上实行的操纵排入队列。_extends handlerhttps://g.csdnimg.cn/static/logo/favicon32.icohttps://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://g.csdnimg.cn/static/logo/favicon32.icohttps://shuaici.blog.csdn.net/article/details/129348678
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]