Flutter for OpenHarmony

一、插件介绍

testcamera是一个专为OpenHarmony优化的Flutter相机测试工具,用于在Flutter应用中实现相机预览功能。该工具通过Flutter的Texture组件与鸿蒙原生相机API交互,提供了简单易用的相机预览接口,适配鸿蒙系统的相机能力。

主要功能特点:

  • 支持OpenHarmony API 9+平台
  • 实现相机预览功能
  • 自动处理相机权限申请
  • 支持Texture纹理渲染
  • 适配鸿蒙系统的原生相机API
  • 提供简洁的Flutter API接口

二、环境准备

系统要求:

  • OpenHarmony API 9+
  • Flutter SDK 3.0+
  • DevEco Studio 3.0+

三、插件使用

3.1 依赖引入

由于该三方库为自定义修改版本,需要以git形式引入。在项目的pubspec.yaml文件中添加以下配置:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  camera:
    git:
      url: "https://atomgit.com/flutter/plugins"
      path: "packages/camera/camera"

然后执行flutter pub get命令获取依赖。

3.2 权限配置

在OpenHarmony项目中,需要在module.json5文件中声明相机和麦克风权限:

{
  "module": {
    "requestPermissions": [
      {"name": "ohos.permission.CAMERA"},
      {"name": "ohos.permission.MICROPHONE"}
    ]
  }
}

3.3 API调用示例

3.3.1 相机预览页面实现

创建一个相机预览页面,使用Texture组件显示相机画面:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class CameraPage extends StatefulWidget {
  const CameraPage({super.key});
  
  _CameraPageState createState() => _CameraPageState();
}

class _CameraPageState extends State<CameraPage> {
  final MethodChannel _channel = MethodChannel('CameraControlChannel');
  int textureId = -1;

  
  void initState() {
    super.initState();
    newTexture();
    startCamera();
  }

  
  void dispose() {
    super.dispose();
    if (textureId >= 0) {
      _channel.invokeMethod('unregisterTexture', {'textureId': textureId});
    }
  }

  void startCamera() async {
    await _channel.invokeMethod('startCamera');
  }

  void newTexture() async {
    int id = await _channel.invokeMethod('registerTexture');
    setState(() {
      this.textureId = id;
    });
  }

  Widget getTextureBody(BuildContext context) {
    return Container(
      width: 500,
      height: 500,
      child: Texture(
        textureId: textureId,
      ),
    );
  }

  
  Widget build(BuildContext context) {
    Widget body = textureId >= 0 ? getTextureBody(context) : Text('loading...');
    print('build textureId :$textureId');

    return Scaffold(
      appBar: AppBar(
        title: Text("相机预览"),
      ),
      body: Container(
        color: Colors.white,
        height: 500,
        child: Center(
          child: body,
        ),
      ),
    );
  }
}
3.3.2 应用入口配置

在应用入口文件中配置相机预览页面:

import 'package:flutter/material.dart';
import 'CameraPage.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '相机测试应用',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CameraPage(),
    );
  }
}
3.3.3 鸿蒙原生插件实现

鸿蒙原生插件实现了相机的初始化、预览和权限处理等功能:

import { FlutterPlugin, FlutterPluginBinding } from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin';
import { TextureRegistry } from '@ohos/flutter_ohos/src/main/ets/view/TextureRegistry';
import MethodChannel from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
import Log from '@ohos/flutter_ohos/src/main/ets/util/Log';
import { checkPermissions, reqPermissionsFromUser, permissions } from './PermissionUtil';
import { getCameraManager, getCameraDevices, getCameraInput, getSupportedOutputCapability, getPreviewOutput, getCaptureSession, beginConfig, setSessionCameraInput, setSessionPreviewOutput, startSession } from './CameraUtil';
import { common } from '@kit.AbilityKit';

const TAG = "CameraPlugin";
export class CameraPlugin implements FlutterPlugin {
  private binding: FlutterPluginBinding | null = null;
  private mMethodChannel: MethodChannel | null = null;
  private textureRegistry: TextureRegistry | null = null;
  private textureId: number = -1;
  private surfaceId: number = -1;

  getUniqueClassName(): string {
    return TAG;
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    Log.e(TAG, "CameraPlugin onAttachedToEngine");
    this.binding = binding;
    this.mMethodChannel = new MethodChannel(binding.getBinaryMessenger(), "CameraControlChannel");
    this.mMethodChannel.setMethodCallHandler((call, result) => {
      let method: string = call.method;
      Log.e(TAG, "Received '" + method + "' message.");
      switch (method) {
        case "registerTexture":
          this.registerCameraTexture();
          result.success(this.textureId);
          break;
        case "startCamera":
          this.startCamera();
          result.success(null);
          break;
        case "unregisterTexture":
          this.unregisterTexture(call.argument("textureId"));
          result.success(null);
          break;
      }
    });
    this.textureRegistry = binding.getTextureRegistry();
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    this.binding = null;
    this.mMethodChannel = null;
  }

  registerCameraTexture(): void {
    Log.i(TAG, "start register Camera texture in flutter engine");
    this.textureId = this.textureRegistry!.getTextureId();
    this.surfaceId = this.textureRegistry!.registerTexture(this.textureId)!.getSurfaceId();
  }

  unregisterTexture(textureId: number): void {
    this.textureRegistry!.unregisterTexture(textureId);
  }

  startCamera() {
    checkPermissions(permissions).then((value: boolean) => {
      if (value) {
        this.startSession();
      } else {
        reqPermissionsFromUser(permissions, getContext(this) as Context).then((value: boolean) => {
          if (value) {
            this.startSession();
          } else {
            console.log(`[camera test] 授权失败`);
          }
        });
      }
    });
  }

  startSession() {
    console.log(`[camera test] 已经授权,相机开始拍摄`);
    let cameraManager = getCameraManager(getContext(this) as common.BaseContext);
    let cameraDevices = getCameraDevices(cameraManager);
    let cameraInput = getCameraInput(cameraDevices[0], cameraManager);
    if (cameraInput != null) {
      getSupportedOutputCapability(cameraDevices[0], cameraManager, cameraInput)
        .then((supportedOutputCapability) => {
          if (supportedOutputCapability != undefined) {
            let previewOutput = getPreviewOutput(cameraManager, supportedOutputCapability, this.surfaceId.toString());
            let captureSession = getCaptureSession(cameraManager);
            if (captureSession != undefined && previewOutput != undefined && cameraInput != null) {
              beginConfig(captureSession);
              setSessionCameraInput(captureSession, cameraInput);
              setSessionPreviewOutput(captureSession, previewOutput);
              startSession(captureSession);
            }
          }
        });
    }
  }
}

3.4 相机工具类

相机工具类封装了鸿蒙原生相机API的调用:

import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';

// 获取相机管理器
export function getCameraManager(context: common.BaseContext): camera.CameraManager {
  let cameraManager: camera.CameraManager = camera.getCameraManager(context);
  return cameraManager;
}

// 获取相机设备列表
export function getCameraDevices(cameraManager: camera.CameraManager): Array<camera.CameraDevice> {
  let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
  if (cameraArray != undefined && cameraArray.length <= 0) {
    console.error("[camera test] cameraManager.getSupportedCameras error");
    return [];
  }
  return cameraArray;
}

// 获取相机输入流
export function getCameraInput(cameraDevice: camera.CameraDevice, cameraManager: camera.CameraManager): camera.CameraInput | null {
  let cameraInput: camera.CameraInput | null = null;
  try {
    cameraInput = cameraManager.createCameraInput(cameraDevice);
    cameraInput.on('error', cameraDevice, (error: BusinessError) => {
      console.info(`[camera test] Camera input error code: ${error.code}`);
    });
  } catch (error) {
    let err = error as BusinessError;
    console.error('[camera test] Failed to createCameraInput errorCode = ' + err.code);
  }
  return cameraInput;
}

// 获取相机预览输出流
export function getPreviewOutput(cameraManager: camera.CameraManager, cameraOutputCapability: camera.CameraOutputCapability, surfaceId: string): camera.PreviewOutput | undefined {
  let previewProfilesArray: Array<camera.Profile> = cameraOutputCapability.previewProfiles;
  let previewOutput: camera.PreviewOutput | undefined = undefined;
  try {
    previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId);
  } catch (error) {
    let err = error as BusinessError;
    console.error("[camera test] Failed to create the PreviewOutput instance. error code: " + err.code);
  }
  return previewOutput;
}

// 启动相机会话
export async function startSession(captureSession: camera.CaptureSession): Promise<void> {
  try {
    await captureSession.commitConfig();
  } catch (error) {
    let err = error as BusinessError;
    console.error(`[camera test] Failed to commitConfig. error: ${JSON.stringify(err)}`);
  }

  try {
    await captureSession.start()
  } catch (error) {
    let err = error as BusinessError;
    console.error(`[camera test] Failed to start. error: ${JSON.stringify(err)}`);
  }
}

四、测试方法

4.1 编译运行应用

使用DevEco Studio编译并运行应用,应用启动后会自动请求相机权限,权限通过后会显示相机预览画面。

4.2 权限测试

首次运行应用时,会弹出相机和麦克风权限请求对话框,用户需要点击"允许"才能使用相机功能。

4.3 功能测试

应用启动后,会在屏幕中央显示一个500x500的相机预览区域,显示当前相机的实时画面。

五、常见问题与解决方案

5.1 相机权限被拒绝

可能原因

  • 用户拒绝了相机权限
  • 应用未在module.json5中声明相机权限

解决方案

  • 重新运行应用,点击"允许"授予相机权限
  • 检查module.json5中是否正确声明了相机和麦克风权限

5.2 相机预览不显示

可能原因

  • Texture组件未正确初始化
  • 相机设备被其他应用占用
  • 相机API调用失败

解决方案

  • 检查Texture组件的textureId是否正确设置
  • 关闭其他可能占用相机的应用
  • 查看日志,排查相机API调用失败的原因

5.3 应用崩溃

可能原因

  • 相机权限未正确处理
  • 相机API调用异常
  • Texture组件使用不当

解决方案

  • 检查权限处理逻辑
  • 查看日志,定位崩溃原因
  • 确保Texture组件在dispose时正确释放资源

六、总结

testcamera是一个专为OpenHarmony平台优化的Flutter相机测试工具,通过本指南,你可以了解如何:

  1. 以git形式引入自定义修改的相机依赖
  2. 在OpenHarmony项目中配置相机权限
  3. 使用Flutter的Texture组件实现相机预览
  4. 处理相机权限申请
  5. 与鸿蒙原生相机API交互

该工具为开发者提供了在OpenHarmony平台上使用Flutter实现相机功能的完整示例,帮助他们快速集成相机功能到自己的应用中。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐