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() 。把这四点做到位,既稳又不漏。

相关推荐
学历真的很重要14 分钟前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
NAGNIP3 小时前
机器学习特征工程中的特征选择
算法·面试
J_liaty3 小时前
RabbitMQ面试题终极指南
开发语言·后端·面试·rabbitmq
NAGNIP3 小时前
机器学习中的数据预处理方法大全!
算法·面试
a程序小傲4 小时前
得物Java面试被问:方法句柄(MethodHandle)与反射的性能对比和底层区别
java·开发语言·spring boot·后端·python·面试·职场和发展
笔COOL创始人5 小时前
requestAnimationFrame 动画优化实践指南
前端·javascript·面试
UrbanJazzerati5 小时前
统计学基础与数据可视化实战——基本图表(1)
面试
小当家.1058 小时前
JVM八股详解(上部):核心原理与内存管理
java·jvm·学习·面试
heartbeat..8 小时前
Spring 声明式事务:原理、使用及失效场景详解
java·spring·面试·事务
xiaoxue..8 小时前
把大模型装进自己电脑:Ollama 本地部署大模型完全指南
javascript·面试·node.js·大模型·ollama