Handler线程模型与内存

1) 线程模型:Handler 绑定 Looper,跨线程=投递到对方队列

  • 一个线程最多一个 Looper ,里面有一个 MessageQueue ;Looper.loop() 在该线程串行取消息执行。
  • Handler(Looper) 决定了回调在哪个线程跑 ;不管你从哪个线程 post/send,最终都在 Handler 绑定的 Looper 线程执行。
  • 跨线程切换 :就是把任务丢进目标线程的 MessageQueue。
scss 复制代码
// 切到主线程
val main = Handler(Looper.getMainLooper())
main.post { /* 这里一定在 UI 线程 */ }

// 专用后台线程
val ht = HandlerThread("worker").apply { start() }
val worker = Handler(ht.looper)
worker.post { /* 后台串行任务 */ }

并发 vs 串行:同一个 Looper 里的任务严格按队列执行,不会并行;想并行要多个线程或线程池。


2) Message 复用与内存

  • 为什么用 obtain :Message.obtain()/Handler.obtainMessage() 走对象池(框架维护的小池),避免频繁 new。
  • 发送后不要再改 msg :入队后可能被并发消费,自行复用/手动 recycle() 会踩内存;让框架在派发后自动回收。
  • 尽量少放大对象 :obj 指向大 bitmap/大数组,会被队列强引用直到执行完。推荐用 what/arg1/arg2 或放轻量 token,大对象交给共享缓存或弱引用。
scss 复制代码
// 推荐:轻量 payload
handler.obtainMessage(WHAT_SAVE, id /*arg1*/, 0).sendToTarget()

3) 生命周期与泄漏(重点)

3.1 非静态内部类 Handler / Runnable 泄漏 Activity

  • 非静态内部类、匿名 Runnable 会隐式持有外部 Activity 。如果消息延时很久 或队列被阻塞,就可能在页面退出后仍被引用 → 泄漏。
  • 解法 A:静态 + WeakReference
scala 复制代码
static class UiHandler extends Handler {
    private final WeakReference<Activity> ref;
    UiHandler(Activity a) { super(Looper.getMainLooper()); ref = new WeakReference<>(a); }
    @Override public void handleMessage(Message msg) {
        Activity act = ref.get(); if (act == null) return;
        // ...
    }
}
  • 解法 B:生命周期清理(最实用)****

    在 onDestroy()(或 onStop())清掉尚未执行的消息/回调:

kotlin 复制代码
class MyActivity : AppCompatActivity() {
    private val h = Handler(Looper.getMainLooper())
    private val token = Any() // 批量标记

    override fun onCreate(b: Bundle?) {
        super.onCreate(b)
        h.postAtTime({ /* ... */ }, token, SystemClock.uptimeMillis() + 5_000)
    }

    override fun onDestroy() {
        h.removeCallbacksAndMessages(null)      // 全部清除
        // 或只清此页相关:
        // h.removeCallbacksAndMessages(token)
        super.onDestroy()
    }
}
  • 说明:postAtTime(Runnable, token, when) / removeCallbacksAndMessages(token) 用同一 token成组移除。

  • 解法 C:直接用生命周期感知(优先选):

    • View.post {}:View 销毁后其 Handler 也随之失效;

    • 协程 lifecycleScope + Dispatchers.Main,在 onDestroy 自动取消;

    • LifecycleOwner + DefaultLifecycleObserver 中统一清理。

3.2 延时消息在页面退出后仍触发

  • 根因:消息与 Runnable 被队列持有。
  • 规避:退出时清理(如上);避免长延时;必要时把任务迁到进度可控的后台组件(WorkManager/Service)。

4) 退出与清理(后台 Looper 线程)

  • Looper.quit()立刻 退出,队列里未到期消息会被丢弃
  • Looper.quitSafely() :处理完所有到期的同步消息后退出(更温和)。
  • HandlerThread 清理一定在不用时 quitSafely() + join(),确保线程结束、释放栈和队列:
scss 复制代码
val ht = HandlerThread("worker").apply { start() }
val h = Handler(ht.looper)

// ... 使用

h.removeCallbacksAndMessages(null) // 先清队列(可选)
ht.quitSafely()                    // 请求退出
ht.join()                          // 等线程收尾,防泄漏
  • 不可复活 Looper :quit 之后这个 Looper 就"死"了;要用就新建 HandlerThread。对已退出的 Looper 发消息会抛 IllegalStateException。

5) 进阶:优先级与阻塞的影响(简述)

  • 同一 Looper 下所有 Handler 共享一个队列 ,按 when 与入队顺序执行;某个回调里做耗时阻塞后续所有消息(包括 UI 绘制),造成卡顿/ANR。
  • UI 线程要保持小而快:重活丢后台;必要时用 IdleHandler 做低优先级清理。
  • 不要滥用 sendMessageAtFrontOfQueue / "异步消息"插队,以免打乱渲染节拍。

6) 小抄(Anti-pattern vs Best Practice)

Anti-pattern

  • 非静态内部 Handler/匿名 Runnable 持有 Activity。

  • Message.obj 塞大对象,或循环复用已发送的 Message。

  • HandlerThread 不退出;在 UI 线程执行重活。

Best Practice

  • Handler(Looper.getMainLooper()) + onDestroy() 里 removeCallbacksAndMessages(null)。
  • 用 Message.obtain()/Handler.obtainMessage();payload 轻量化。
  • 后台串行:HandlerThread(用完 quitSafely()+join());并行:线程池/协程。
  • Lifecycle/Coroutine 优先:viewLifecycleOwner.lifecycleScope.launch { ... }。

一句话总结:Handler 决定"在哪个线程执行"Message 用 obtain 复用且轻量化页面销毁要清队列 ;后台 Looper 线程记得 quitSafely()+join() 。把这四点做到位,既稳又不漏。

相关推荐
WYiQIU4 小时前
面了一次字节前端岗,我才知道何为“造火箭”的极致!
前端·javascript·vue.js·react.js·面试
努力学算法的蒟蒻4 小时前
day20(11.21)——leetcode面试经典150
面试
纵有疾風起7 小时前
C++——多态
开发语言·c++·经验分享·面试·开源
ANYOLY8 小时前
Redis 面试题库
java·redis·面试
顾林海8 小时前
从0到1搭建Android网络框架:别再让你的请求在"路上迷路"了
android·面试·架构
拉不动的猪8 小时前
前端三大权限场景全解析:设计、实现、存储与企业级实践
前端·javascript·面试
uhakadotcom9 小时前
Loguru 全面教程:常用 API 串联与实战指南
后端·面试·github
培风图南以星河揽胜11 小时前
Java实习模拟面试|离散数学|概率论|金融英语|数据库实战|职业规划|期末冲刺|今日本科计科要闻速递:技术分享与学习指南
java·面试·概率论
艾斯比的日常13 小时前
JVM 内存结构:全面解析与面试重点
jvm·面试·职场和发展
gadiaola13 小时前
【计算机网络面试篇】HTTP
java·后端·网络协议·计算机网络·http·面试