feat(app): 激活模型变化时重建推理 worker(无模型仅预览并提示选择)

ModelManager 激活集变化(下载完成自动激活/取消激活/目录刷新)时经
_onModelsChanged 触发 _reloadWorker:激活集未变直接跳过,变了则重建
worker+analyzer 并重启帧流;无激活模型时仅预览,横幅区分
「未使用模型:点右上角设置…」与「识别模型加载失败…」。
This commit is contained in:
2026-09-01 09:06:48 +08:00
parent 9a46bdc6c0
commit 0bd1af3b42
+58 -18
View File
@@ -38,6 +38,10 @@ class _CameraScreenState extends State<CameraScreen> {
Map<dynamic, dynamic> _nativeStats = const {}; Map<dynamic, dynamic> _nativeStats = const {};
Timer? _statsTimer; Timer? _statsTimer;
/// 当前 worker 已加载的模型 id 集合(激活集变化对比用)
Set<int> _loadedModelIds = const {};
bool _reloadingWorker = false;
void _openSettings() { void _openSettings() {
final vm = _viewModel; final vm = _viewModel;
if (vm == null) return; if (vm == null) return;
@@ -116,6 +120,8 @@ class _CameraScreenState extends State<CameraScreen> {
WakelockPlus.enable(); WakelockPlus.enable();
// 每秒轮询原生侧帧状态:无帧时诊断行也能实时刷新(camErr/计数) // 每秒轮询原生侧帧状态:无帧时诊断行也能实时刷新(camErr/计数)
_statsTimer = Timer.periodic(const Duration(seconds: 1), (_) => _pollStats()); _statsTimer = Timer.periodic(const Duration(seconds: 1), (_) => _pollStats());
// 模型清单激活集变化(下载完成自动激活/取消激活)时重建推理 worker
ModelManager.instance.addListener(_onModelsChanged);
} }
Future<void> _pollStats() async { Future<void> _pollStats() async {
@@ -132,8 +138,7 @@ class _CameraScreenState extends State<CameraScreen> {
setState(() => _permissionGranted = granted); setState(() => _permissionGranted = granted);
if (!granted) return; if (!granted) return;
// 模型热更新:优先使用已下载的数据集模型(启动时后台拉取;此处兜底等待, // 模型热更新:启动拉取目录(只拉不下载),此处兜底等待;未下载模型时仅预览
// 下载慢/失败不阻塞相机启动——无下载模型时仅预览)
if (!ModelManager.instance.ready) { if (!ModelManager.instance.ready) {
try { try {
await ModelManager.instance await ModelManager.instance
@@ -141,23 +146,55 @@ class _CameraScreenState extends State<CameraScreen> {
.timeout(const Duration(seconds: 15)); .timeout(const Duration(seconds: 15));
} catch (_) {} } catch (_) {}
} }
// 模型加载/推理在后台 isolate,不阻塞 UIworker 为 null 时仅预览并提示 await _reloadWorker();
final worker = await DetectorWorker.create( await _startCamera();
models: ModelManager.instance.models); }
final viewModel = CameraViewModel(reminder: Reminder());
viewModel.setModelReady(worker != null); /// 用当前激活模型重建推理 worker(激活集变化/启动时调用);
final analyzer = FrameAnalyzer(worker: worker, viewModel: viewModel); /// worker 为 null(无激活模型或加载失败)时仅预览并提示。
if (!mounted) { Future<void> _reloadWorker() async {
analyzer.dispose(); if (_reloadingWorker) return;
viewModel.dispose(); _reloadingWorker = true;
try {
final models = ModelManager.instance.models;
final ids = models.map((m) => m.datasetId).toSet();
if (_viewModel != null &&
_loadedModelIds.length == ids.length &&
_loadedModelIds.containsAll(ids)) {
return; // 激活集未变(进度/目录刷新通知)直接跳过
}
final worker = await DetectorWorker.create(models: models);
_loadedModelIds = ids;
final vm = _viewModel ?? CameraViewModel(reminder: Reminder());
vm.setModelReady(worker != null);
final analyzer = FrameAnalyzer(worker: worker, viewModel: vm);
final old = _analyzer;
if (!mounted) {
analyzer.dispose();
if (vm != _viewModel) vm.dispose();
return;
}
setState(() {
_viewModel = vm;
_analyzer = analyzer;
});
old?.worker?.dispose();
// 相机已启动:重启帧流绑定新 analyzer(start 内部先 stop 再订阅)
if (mounted && _cameraController != null) {
await _cameraController!.start(analyzer);
}
} finally {
_reloadingWorker = false;
}
}
void _onModelsChanged() {
final ids = ModelManager.instance.models.map((m) => m.datasetId).toSet();
if (_loadedModelIds.length == ids.length &&
_loadedModelIds.containsAll(ids)) {
return; return;
} }
setState(() { _reloadWorker();
_viewModel = viewModel;
_analyzer = analyzer;
});
await _startCamera();
} }
Future<void> _startCamera() async { Future<void> _startCamera() async {
@@ -193,6 +230,7 @@ class _CameraScreenState extends State<CameraScreen> {
@override @override
void dispose() { void dispose() {
ModelManager.instance.removeListener(_onModelsChanged);
_statsTimer?.cancel(); _statsTimer?.cancel();
WakelockPlus.disable(); WakelockPlus.disable();
_cameraController?.stop(); _cameraController?.stop();
@@ -333,7 +371,9 @@ class _CameraScreenState extends State<CameraScreen> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
'识别模型加载失败:${DetectorWorker.lastLoadError ?? '未知原因'}\n最后步骤:${DetectorWorker.lastLog ?? '-'}', ModelManager.instance.models.isEmpty
? '未使用模型:点右上角设置,在「模型清单」中选择要使用的模型'
: '识别模型加载失败:${DetectorWorker.lastLoadError ?? '未知原因'}\n最后步骤:${DetectorWorker.lastLog ?? '-'}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(color: Colors.orange, fontSize: 14), style: const TextStyle(color: Colors.orange, fontSize: 14),
), ),