直接回答
应把每个 CameraX ImageProxy 当作短生命周期资源,为它指定一个明确所有者和唯一关闭点。Android CameraX 图像分析指南要求 analyzer 完成分析后调用 ImageProxy.close(),并提醒不要直接关闭其包装的 Media.Image。如果本地 AI 管线会在异步推理前复制像素或转换帧,应在应用不再需要 CameraX 图像平面时关闭代理;后续任务只持有有界的应用自有数据。除非推理时长和背压策略经过有意设计与测试,否则不要让相机帧一直存活到模型推理结束。 资源泄漏可能表现为图像分析停滞、内存持续上升、处理过期帧,或相机难以恢复。但这些症状不一定都证明 ImageProxy 泄漏:analyzer 也可能处理太慢,队列策略可能阻塞,推理可能长期持有复制后的位图,或只有异常路径漏了清理。应记录所有权转换:收到、复制、提交、完成、取消、失败、关闭,并给每帧唯一标识。确认每个已接收帧最终只关闭一次;被拒绝或丢弃的帧也要释放。不要用一个忙碌布尔值代表所有帧,否则回调并发时无法知道它指向谁。 应分开管理图像采集、预处理和推理所有权。analyzer 接收 CameraX 代理;预处理组件可能生成应用自有位图或张量;推理任务则在完成前持有该复制数据。复制策略必须写明,因为复制全分辨率帧也会增加内存。项目 MEMORY-01 事实指出,输入张量、运行时缓冲和分配器行为都会影响内存,因此本文不给出通用缓冲数量或 RAM 阈值。队列应有界,背压策略应有文档。模型跟不上帧率时,实时相机界面可能更适合丢弃旧帧,而不是积累延迟;但产品必须明确哪些帧需要保留。 生命周期和错误路径与成功路径同样重要。将用例绑定到生命周期所有者,结束采集时清除 analyzer 或解绑 use case;取消任务时,应根据所有权分别释放代理与应用自有复制数据。即使颜色转换、OCR 解析、模型初始化或界面状态收集抛错,也要在 finally 路径关闭代理。不能让 analyzer 与协程完成回调同时争抢关闭同一代理。如果推理任务在页面消失后继续运行,就让较长生命周期的任务所有者持有复制输入,并为用户停止操作定义清晰清理边界。 测试可使用短时间重复采集脚本,并注入慢推理、异常、取消、页面导航、相机解绑和进程重建。跟踪未结束代理数、应用复制缓冲数、帧龄、丢帧事件和相机重新启动结果;这些是测试观察值,不是普遍性能指标。可通过代码审查或强制 close-once 的包装器核对所有权。测试语料不要包含私人照片,也不能把一台手机上的内存轨迹外推为全面设备兼容或性能结论。有效产出应是一条可复现的所有权时间线,显示每个缓冲何时释放,以及失败后相机是否能恢复。 图像数据跨越线程时还要处理超时与结果过期。一个任务即使晚些时候完成,也可能属于已经关闭的页面或用户不再查看的旧帧。任务标识和帧序号应随复制缓冲一起传递;结果回到 UI 前要确认其仍对应当前会话。过期输出应丢弃或另行标记,而不是覆盖新帧的结果。这样既减少不必要的积压,也避免把生命周期竞态误诊为 OCR 模型随机改变。 重启测试应覆盖相机 use case 解绑、重新绑定、权限暂时不可用、推理取消以及组件异常后的恢复。每一步都要确认相机资源释放和应用自有图像数据被清理;不要通过重启整个 Activity 来掩盖重复持有。公开报告保留帧 ID、阶段、资源计数和错误类别即可,不能附原始照片或完整识别文字。对用户而言,失败应有可理解提示与重试/退出操作;对工程团队而言,追踪字段要足以找到哪一个所有者漏了关闭。
连续本地 OCR 后相机预览停止
Android 屏幕将 CameraX 帧送入预处理和本地模型。短演示正常,但慢推理或页面导航后预览停滞。团队不确定是代理未关闭、复制像素堆积,还是 analyzer 队列积压。
决策
为每个 ImageProxy 指定单一所有者和终止关闭点,限制应用自有复制缓冲及队列,并用帧 ID 测试所有异常出口。
预期结果
团队能区分相机缓冲泄漏、推理变慢和复制数据保留,并验证安全停止/重新绑定流程,而不发布全机型内存主张。
需要收集的证据
只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:
- MEMORY-01:推理内存还受输入张量、运行时缓冲与分配器影响,不能由模型文件大小推断。
- COVE-OCR-01:Cove Photo 将可见文本结构化存储,并提供复制与 OCR 历史导出。
设备兼容结论须绑定具体设备、系统、模型、运行时、后端和工作负载,不能靠参数量推测。
实施流程
- 画出所有权时间线. 对每帧列明 CameraX 代理所有者、像素/位图/张量复制所有者、推理任务所有者和 UI 结果所有者。定义 CameraX 图像平面何时不再需要,以及由谁调用 ImageProxy.close()。
- 只复制必须跨出 analyzer 生命周期的数据. 异步推理需要 analyzer 返回后继续使用像素时,创建有界的应用自有表示,并记录尺寸和生命周期。复制成功后立即关闭代理;复制失败也要在 finally 中关闭。没有测量理由时,不要持有多张全分辨率帧。
- 选择队列和背压行为. 配置并记录 ImageAnalysis 背压策略和队列深度。为慢推理定义明确策略:处理最新帧、丢弃旧帧或串行化;每个被丢弃/拒绝的代理都要关闭,每个已提交任务都要有取消所有者。
- 注入失败与生命周期出口. 测试成功、预处理异常、模型加载失败、推理取消、快速导航、替换 analyzer、解绑和页面重建。记录帧 ID、关闭次数、复制缓冲释放情况和每个出口后的未完成工作。
- 验证内存边界与相机恢复. 重复固定的合成或已获准图像序列,检查未释放资源是否有界,而非推导通用 RAM 限额;停止后重新绑定相机。报告只对应被测设备和构建,诊断不保存个人照片或原始 OCR 文本。
让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。
验收标准
- 每个已接收、拒绝或丢弃的 ImageProxy 都有且只有一个关闭路径,涵盖异常和取消。
- analyze() 结束后仍需使用的数据是明确归应用所有的复制缓冲,并有队列和保留上限。
- 测试覆盖 analyzer 失败、慢推理、导航/解绑、取消和相机重启,不会积累未完成帧。
- 内存或性能结果限定到测试配置,不声称普遍机型支持或固定 RAM 阈值。
决策对照表
| 维度 | 选择或证据 | 解读方式 |
|---|---|---|
| 推理期间持有 ImageProxy | 除非所有权与时延严格有界,否则避免 | 延长相机资源生命;推理变慢时可能让生产者缺少可用帧。 |
| 复制后关闭代理 | 异步处理确实需要像素时使用有界应用缓冲 | 分离 CameraX 与推理生命周期;复制仍占内存且必须清理。 |
| 排队保留每一帧 | 只有产品需要且容量经过测试时才采用 | 容易积累过期帧;实时预览可按明确策略丢弃旧帧。 |
需要避免的失败模式
- 推理尚未完成时长期持有相机 ImageProxy,阻塞生产者。
- 只在成功路径关闭代理,异常、取消和页面退出后留下未释放帧。
还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。
最小复现记录
| 层级 | 记录内容 |
|---|---|
| 设备 | 厂商、型号、芯片、内存档位、操作系统版本 |
| 软件 | 应用版本和 Git 提交号 |
| 模型 | 系列、变体、版本、格式、文件长度、摘要、量化方式 |
| 运行时 | 名称、版本、请求后端及实际后端证据 |
| 工作负载 | 样例版本、输入摘要、提示词摘要、输出策略 |
| 结果 | 完成、失败、取消、回退及保护隐私的诊断信息 |
发布检查表
- ☐ 主要搜索问题已回答,且没有未经支持的数字。
- ☐ 所有模型文件和运行时版本均已固定。
- ☐ 代表性任务和失败处理策略均有说明。
- ☐ 生命周期、取消、清理和回退均已测试。
- ☐ 用户内容与网络边界均有说明。
- ☐ 结论只适用于被记录的配置。
- ☐ 新增测试结果时链接到原始方法或证据。
来源与相关证据
中文部署与排错专题见。奇连 AI 端侧专题。
常见问题
ImageProxy 应由谁关闭?
接收所有权的 analyzer/应用代码应在不再需要相机缓冲时调用 ImageProxy.close()。不要直接关闭包装的 Media.Image,也不要让两个回调争抢关闭责任;每帧只关闭一次。
可以把 ImageProxy 传给后台推理协程吗?
只有在所有权和生命周期明确、并确保任务期间代理持续有效时才可以。多数异步管线更适合把所需像素复制到有界应用缓冲,再在长时间推理前关闭代理。
预览停滞是否证明 ImageProxy 泄漏?
不能。慢分析、背压设置、推理积压、复制数据保留和生命周期错误都可能类似。为帧记录身份与释放事件,再注入延时和失败以找出缺失的转换。