Flutter安卓实时目标检测应用报错:FlutterJNI与Native C++ detached
我正在开发一款基于自定义模型的Android实时目标检测Flutter应用,已添加模型,但运行代码时出现如下错误日志:
W/FlutterJNI(21251): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: plugins.flutter.io/camera_android/imageStream. Response ID: 783
我是Flutter新手,查阅相关资料后仍无法解决问题。以下是项目相关代码:
依赖配置
dependencies: flutter: sdk: flutter camera: ^0.10.6 get: ^4.6.6 permission_handler: ^11.3.1 flutter_tflite: ^1.0.1 image: ^3.2.0
build.gradle(Android)
android { namespace "com.example.beet" compileSdkVersion 34 ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } aaptOptions { noCompress 'tflite' noCompress 'lite' } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { applicationId "com.example.beet" minSdkVersion 26 targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } }
ScanController代码
import 'dart:typed_data'; import 'package:camera/camera.dart'; import 'package:flutter_tflite/flutter_tflite.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:image/image.dart' as img; class ScanController extends GetxController { late CameraController cameraController; late List<CameraDescription> cameras; var isCameraInitialized = false.obs; var cameraCount = 0; var x = 0.0, y = 0.0, w = 0.0, h = 0.0; var label = ""; @override void onInit() { super.onInit(); initCamera(); initTFLite(); } @override void dispose() { cameraController.dispose(); super.dispose(); } Future<void> initCamera() async { if (await Permission.camera.request().isGranted) { cameras = await availableCameras(); cameraController = CameraController(cameras[0], ResolutionPreset.max); try { await cameraController.initialize().then((_) { isCameraInitialized(true); cameraController.startImageStream((image) { cameraCount++; if (cameraCount % 10 == 0) { cameraCount = 0; objectDetector(image); } }); }); } catch (e) { print("Camera initialization error: $e"); } } else { print("Camera permission denied"); } update(); } Future<void> initTFLite() async { try { await Tflite.loadModel( model: "assets/best.tflite", labels: "assets/label.txt", isAsset: true, numThreads: 1, useGpuDelegate: false, ); print("Model loaded successfully"); } catch (e) { print("Error loading model: $e"); } } Uint8List imageToByteListFloat32(img.Image image, int inputSize) { var convertedBytes = Float32List(1 * inputSize * inputSize * 3); var buffer = Float32List.view(convertedBytes.buffer); int pixelIndex = 0; for (var i = 0; i < inputSize; i++) { for (var j = 0; j < inputSize; j++) { var pixel = image.getPixel(j, i); buffer[pixelIndex++] = (img.getRed(pixel) / 127.5) - 1; buffer[pixelIndex++] = (img.getGreen(pixel) / 127.5) - 1; buffer[pixelIndex++] = (img.getBlue(pixel) / 127.5) - 1; } } return convertedBytes.buffer.asUint8List(); } Future<void> objectDetector(CameraImage image) async { print("Starting object detection"); final int width = image.width; final int height = image.height; // Convert CameraImage to Image package format img.Image imgImage = img.Image(width, height); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { final int uvIndex = (i ~/ 2) * (image.planes[1].bytesPerRow ~/ 2) + (j ~/ 2); final int index = i * width + j; final y = image.planes[0].bytes[index]; final u = image.planes[1].bytes[uvIndex]; final v = image.planes[2].bytes[uvIndex]; imgImage.setPixel(j, i, yuvToRgb(y, u, v)); } } // Resize the image to the size your model expects img.Image resizedImage = img.copyResize(imgImage, width: 224, height: 224); Uint8List input = imageToByteListFloat32(resizedImage, 224); var detector = await Tflite.runModelOnFrame( bytesList: [input], imageHeight: 224, imageWidth: 224, imageMean: 0.0, imageStd: 1.0, numResults: 1, rotation: 90, threshold: 0.4, ); if (detector != null && detector.isNotEmpty) { print("Object detected: $detector"); var ourDetectedObject = detector.first; if (ourDetectedObject['confidenceInClass'] * 100 > 45) { label = ourDetectedObject['detectedClass'].toString(); h = ourDetectedObject['rect']['h']; w = ourDetectedObject['rect']['w']; x = ourDetectedObject['rect']['x']; y = ourDetectedObject['rect']['y']; } else { print("Detection confidence too low"); } } else { print("No objects detected"); } update(); } int yuvToRgb(int y, int u, int v) { int r = (y + (1.370705 * (v - 128))).toInt(); int g = (y - (0.337633 * (u - 128)) - (0.698001 * (v - 128))).toInt(); int b = (y + (1.732446 * (u - 128))).toInt(); r = r.clamp(0, 255); g = g.clamp(0, 255); b = b.clamp(0, 255); return img.getColor(r, g, b); } }
CameraView代码
import 'package:beet1/controller/scan_controller.dart'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; class CameraView extends StatelessWidget { const CameraView({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: GetBuilder<ScanController>( init: ScanController(), builder: (controller) { if (!controller.isCameraInitialized.value) { return const Center(child: Text("Loading Preview")); } return Stack( children: [ CameraPreview(controller.cameraController), Positioned( top: (controller.y * context.height).toDouble(), right: (controller.x * context.width).toDouble(), child: Container( width: (controller.w * context.width).toDouble(), height: (controller.h * context.height).toDouble(), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.green, width: 4.0), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( color: Colors.white, child: Text(controller.label), ), ], ), ), ), ], ); }, ), ); } }
我已查阅相关资料,但仍未能理解解决方法,恳请帮忙解决该问题。
这个错误是因为相机ImageStream在Flutter引擎与Native层连接断开后仍尝试发送消息,核心问题出在相机资源管理和GetX控制器生命周期处理上,以下是具体修复步骤:
1. 修复GetX控制器初始化逻辑
你在CameraView中通过GetBuilder的init参数创建控制器,会导致页面重建时重复实例化控制器,旧控制器的相机流未正确停止引发资源泄漏。修改CameraView:
class CameraView extends StatelessWidget { const CameraView({super.key}); @override Widget build(BuildContext context) { // 用Get.put()确保全局单例,避免重复创建控制器 final controller = Get.put(ScanController()); return Scaffold( body: GetBuilder<ScanController>( builder: (controller) { if (!controller.isCameraInitialized.value) { return const Center(child: Text("Loading Preview")); } return Stack( children: [ CameraPreview(controller.cameraController), Positioned( top: (controller.y * context.height).toDouble(), right: (controller.x * context.width).toDouble(), child: Container( width: (controller.w * context.width).toDouble(), height: (controller.h * context.height).toDouble(), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.green, width: 4.0), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( color: Colors.white, child: Text(controller.label), ), ], ), ), ), ], ); }, ), ); } }
2. 完善相机与模型资源的释放逻辑
在ScanController的dispose方法中,先停止图像流再释放相机,同时关闭TFLite模型避免资源残留:
@override void dispose() { // 先停止图像流,再释放相机资源 if (cameraController.value.isStreamingImages) { cameraController.stopImageStream(); } cameraController.dispose(); // 关闭TFLite模型 Tflite.close(); super.dispose(); }
3. 优化图像流回调的异步处理
objectDetector是异步方法,频繁调用会导致任务堆积,添加标记避免同时处理多个帧:
await cameraController.initialize().then((_) { isCameraInitialized(true); bool isProcessing = false; cameraController.startImageStream((image) { if (isProcessing) return; cameraCount++; if (cameraCount % 10 == 0) { cameraCount = 0; isProcessing = true; objectDetector(image).then((_) { isProcessing = false; }).catchError((_) { isProcessing = false; }); } }); });
4. 升级camera依赖版本
你当前使用的camera: ^0.10.6版本较旧,存在已知的Native通信问题,升级到最新稳定版:
dependencies: camera: ^0.11.0+1 # 可通过flutter pub upgrade camera获取最新版
5. 检查AndroidManifest配置
确保AndroidManifest.xml中添加了必要的相机权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
内容的提问来源于stack exchange,提问作者Nisa Nur Taş

