You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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ş

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:05:54