直接回答
模型加载和推理应避开 Android 主线程,但不能为了让界面不卡就从每次按钮点击随意启动后台任务。应定义一个由应用拥有的执行边界来管理原生运行时,说明请求是串行、排队还是拒绝,并把有边界的进度和结果传递给界面状态。Android 线程指南指出主线程负责分发输入和绘制,不应被长时间操作阻塞;UI 对象也不是线程安全的。因此需要遵循两条分开的规则:昂贵或原生工作放进合适的工作线程边界;界面状态则通过安全且符合生命周期的机制返回。 仅仅换到后台线程,并不能证明原生调用支持并发。底层引擎可能有可变会话状态、共享内存或不可重入的调用。除非所选运行时文档和实现明确证明精确配置支持并行请求,否则应先采用单一所有者和显式串行策略。协程收到取消请求,也不等于阻塞的原生函数已经停止。适配层必须定义如何观察取消、调用能否被中断、何时清理完成,以及什么时候才允许启动下一项任务。在清理确认前,界面即使显示“正在取消”,也不应放行第二个请求去和旧原生操作竞争。 当前 Cove ModelManager 会串行化模型加载。这个事实只描述加载属性,不能扩大成所有推理路径天然线程安全。加载、推理、关闭、界面重建和错误恢复都应分别有生命周期与并发测试。不要把运行时句柄放进 Compose 状态,也不要把可能要跨屏幕存活的进程级引擎绑定到很快会销毁的界面对象。ViewModel 可以维护可观察任务状态;更长生命周期的 repository 或 coordinator 则可负责原生资源。具体分工应与产品需要的任务生命周期一致。 测试要围绕事件顺序,而不只是检查最后出现了一段文本。提交请求后重建界面、取消任务、关闭引擎、再提交新任务,并确认没有重复引擎、陈旧观察者、主线程违规或结果乱序。错误与超时也要作为一等状态。若测量响应性,必须事先定义方法并保存原始运行记录;本指南不声称任何具体加速幅度。目标是让已经测试的配置保持响应性和资源正确性,而不是做全机型性能宣传。 每次从 UI 事件启动工作时,都需要明确状态转移:空闲、等待运行、执行、部分结果、取消请求、清理完成、失败。特别要区分“用户离开页面”与“用户取消任务”:前者可能只停止当前页面观察状态,后者才应向底层任务发送取消。若二者被混为一谈,就会出现页面重建后重复启动或用户已停止但原生资源仍占用的情况。用明确事件和所有权关系记录这些选择,可以让代码评审、故障复现和产品提示保持一致。 任何性能测试都要把设备、系统、模型制品、运行时、后端、输入、内存、温度状态和失败情况一起记录。当前项目事实卡明确缺少完整真实设备基准集,所以这里提供的是线程设计和验收方法,不是帧率、延迟或耗电结论。后台工作策略也不应被误写成可靠持续执行承诺:应用进程可能结束,任务生命周期应按 Android 官方组件模型设计。通过这些限定,读者可以把问题定位到线程、状态所有者、原生取消能力或系统生命周期,而不是套用一个未经证实的万能 dispatcher 建议。
第一次加载模型时按钮导致页面冻结
用户首次点击功能按钮后,应用开始初始化模型并调用原生推理。页面显示加载动画,但点击不再响应;离开页面再回来时,同一任务可能再次提交。团队需要一套覆盖线程、所有权、取消和重复请求的契约。
决策
把工作交给唯一拥有运行时的 coordinator;除非明确支持并发,否则先串行处理;任务状态与页面生命周期分离。
预期结果
界面可以继续渲染状态,重复请求受明确策略控制,取消具有可测试的完成边界。
需要收集的证据
只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:
- COVE-LIFECYCLE-01:当前 Cove ModelManager 对模型加载进行串行化;该事实不等于所有推理调用都线程安全。
- BENCHMARK-01:项目缺少完整真实设备基准数据,只能描述方法,不能声称特定性能提升。
只有记录了模型身份、构建配置、实际后端、任务样例和失败情况,运行时比较才可复核。
实施流程
- 选 dispatcher 前先画清所有权. 标出谁持有引擎、会话、请求队列和 UI 状态。根据运行时阻塞方式选工作执行上下文,同时保证 Compose 或 View 对象不会被带到工作线程。记录哪些所有者跨普通导航存活、哪些会主动关闭。
- 明确并发与背压策略. 选择单请求加排队或拒绝等策略,并在界面中显示状态。不要每次点击都创建无界协程。共享引擎时还要说明会话是否可共享、对话身份如何隔离;没有特定运行时证据之前,不启用并行推理。
- 把取消定义为协议. 写清何时发出取消、原生适配层如何观察、保留哪些部分输出、何时释放缓冲区和会话,以及哪个信号表示可以安全启动新请求。原生调用无法中断时,应显示等待清理,而不是宣称已即时停止。
- 用符合生命周期的方式回传 UI 状态. 通过不可变状态流交给当前可见界面采集。不能从工作线程操作 UI 对象。还要区分界面观察者被取消与底层应用级任务本身被取消。
- 覆盖竞态和响应性边界. 测试双击、导航、界面重建、加载或推理中取消、关闭引擎、错误、超时和下一任务启动,断言事件顺序和资源所有权。记录主线程诊断与明确定义的响应性轨迹,但不要将单次计时外推成全设备性能承诺。
让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。
验收标准
- 模型加载和推理不会在 UI 回调中长时间阻塞;UI 状态通过线程安全的主线程机制更新。
- coordinator 有书面的请求并发策略,不会因连续点击而无界启动原生工作。
- 只有适配层观察到已文档化的停止或清理边界后,才把取消标为完成。
- 界面重建测试证明不会重复创建运行时所有者,也不会误取消应跨屏幕存活的任务。
决策对照表
| 维度 | 选择或证据 | 解读方式 |
|---|---|---|
| 每个 Composable 都启动工作 | 避免 | 组合与重组不是稳定的原生资源所有权边界。 |
| 单一串行运行时 coordinator | 没有反证前的默认方案 | 让所有权、顺序、取消和恢复都清晰且可测试。 |
| 并行原生请求 | 仅在精确运行时能力与竞态测试通过后启用 | 特定设计下可能提高吞吐;若底层共享状态不可并行,则会造成错误。 |
需要避免的失败模式
- 每次按钮点击都启动后台原生任务,形成无界并发。
- 把协程取消请求直接当成阻塞原生调用已经停止。
还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。
最小复现记录
| 层级 | 记录内容 |
|---|---|
| 设备 | 厂商、型号、芯片、内存档位、操作系统版本 |
| 软件 | 应用版本和 Git 提交号 |
| 模型 | 系列、变体、版本、格式、文件长度、摘要、量化方式 |
| 运行时 | 名称、版本、请求后端及实际后端证据 |
| 工作负载 | 样例版本、输入摘要、提示词摘要、输出策略 |
| 结果 | 完成、失败、取消、回退及保护隐私的诊断信息 |
发布检查表
- ☐ 主要搜索问题已回答,且没有未经支持的数字。
- ☐ 所有模型文件和运行时版本均已固定。
- ☐ 代表性任务和失败处理策略均有说明。
- ☐ 生命周期、取消、清理和回退均已测试。
- ☐ 用户内容与网络边界均有说明。
- ☐ 结论只适用于被记录的配置。
- ☐ 新增测试结果时链接到原始方法或证据。
来源与相关证据
中文部署与排错专题见。奇连 AI 端侧专题。
常见问题
把推理放到 Dispatchers.IO 就足够了吗?
不够。dispatcher 能将阻塞调用从 UI 线程移开,但它不会替你定义原生资源所有权、并发、取消、清理和结果顺序。这些需要应用级契约与测试。
取消协程是否会立刻停止原生推理?
不一定。协程取消依赖协作式检查;阻塞原生调用可能需要单独且受支持的中断方式。在适配层确认停止或清理完成前,应把取消显示为处理中。
原生引擎应该由 ViewModel 持有吗?
要根据任务所需生命周期和运行时契约决定。ViewModel 可以暴露界面任务状态,而跨界面重建存活的资源可能需要 repository 或 coordinator 持有。无论怎样选,都要记录并测试其生命周期。