SuperAPI AI 工具集

named-job-lifecycle-discipline

编程与开发

查看项目

更新于 10-17 16:16

技能介绍

该技能面向 Kotlin 中长生命周期对象(例如服务处理器、屏幕模型或会话控制器)的后台工作管理。这类对象会从无法控制、触发频率又常高于预期的回调中启动后台工作;若不加管理,每次触发都可能泄漏一个重复执行同一工作的协程。

主要能力与约定:

  1. 按关注点拆分字段。每个关注点使用一个可为空的 Job 字段,绝不多个关注点共用一个字段。示例中列出 progressJob、bufferedJob、sleepTimerJob、loadJob、metadataJob 等多个独立句柄。

  2. 取消先于重启。每个启动点都以 xJob?.cancel() 开头,并把赋值与取消紧邻写在一起,使赋值和取消不可分割。原因在于“正在播放”之类回调可能重复触发(切换、重缓冲后恢复、重新获得焦点后恢复),泄漏的循环会成倍增加 UI 更新和周期性位置写入。

  3. 拆卸统一处理。在一个代码块中先逐个取消并置空所有字段,再取消 scope 本身;先取消 scope 会让后续每个 job?.cancel() 变成形式,掩盖哪些句柄实际仍存活。

使用方式(检查与验证):

  • 用 grep -rn "Job? = null" --include="*.kt" src/ 列出字段清单,再用 grep -rn "metadataJob" --include="*.kt" src/ 逐个检查每个字段的所有使用处。
  • 把第二类输出成对阅读:每一行赋值前必须紧邻一行取消;没有取消的赋值就是泄漏。
  • 只声明、只取消而从未赋值的字段属于无症状的死代码。文中给出一个实例:某句柄仅在逐个收集器顶部被取消一次,从未被赋值,因此取消始终是空操作,只是下游按 item id 复查悄悄承担了全部工作。
  • 计数验证:在每个周期性循环内加一行日志,反复驱动启动它的回调(多次切换条目、暂停与恢复、制造卡顿),十次与一次后的日志行速率必须相同,任何上升都说明某个启动点缺少取消。对一次性任务,启动 A 后在其返回前切到 B,确认 A 的结果从未进入状态;若进入了,先检查该字段是否根本未被赋值。最后释放对象并确认循环停止,拆卸后仍有日志说明任务被启动到了未被取消的 scope 中。

已知陷阱:

  • stopX() 辅助函数只取消最新句柄,无法弥补启动点缺失的取消:更早的任务会一直运行,表现为同一状态每 tick 写入 2 次、3 次、5 次,像闪烁或始终无法稳定的标志,而非报错。
  • 用裸 Job() 预填字段比留 null 更糟:Job() 构造出的 JobImpl 自创建起即处于 active,而兄弟重启守卫惯用法 if (xJob?.isActive != true) xJob = scope.launch { … } 正好检测这一点,于是被守卫的启动会被跳过,留下永远不会做任何事的占位任务。占位符还会漂移,拆卸中取消的部分字段并不在该初始化器里。
  • 拆卸写入不可被取消:清理若在 collectLatest 动作中运行,更新的上游发射会中途取消该动作并留下半拆卸对象,下一轮会误判为“已在运行”,需用 withContext(NonCancellable) 包裹使其完成,并在重启前清空保留快照以免重放陈旧值。该包装不得用于吞掉取消:应单独捕获 CancellationException 并重新抛出,否则破坏结构化并发。
  • cancel() 立即返回而不等待;当下一步依赖前一步确实已停止(复用其持有的资源或读取其写入)时,应 join() 该句柄。

限制:资料未提供该技能的安装命令、版本要求、兼容平台或收费信息,这些事实暂未提供。