跳到主要内容

在Worker线程中使用相机(ArkTS)

Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与主线程分离,在后台线程中运行一个脚本进行耗时操作,极大避免类似计算密集型或高延迟的任务阻塞主线程的运行。

通常开发者使用相机功能需要创建相机会话,并持续接收处理预览流、拍照流、录像流等从而实现相关相机功能,这些密集型操作如果都放在主线程即UI线程,可能会阻塞UI绘制,推荐开发者在worker线程中实现相机功能。

开发步骤

  1. 导入依赖,本篇文档需要用到worker和相机框架等相关依赖包。

    import { BusinessError } from '@kit.BasicServicesKit';
    import { camera } from '@kit.CameraKit';
    import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
  2. 创建相机服务代理类,调用CameraKit方法都放在这个类里执行。

    class CameraService {
    private imageWidth: number = 1920;
    private imageHeight: number = 1080;
    private cameraManager: camera.CameraManager | undefined = undefined;
    private cameras: Array<camera.CameraDevice> = [];
    private cameraInput: camera.CameraInput | undefined = undefined;
    private previewOutput: camera.PreviewOutput | undefined = undefined;
    private photoOutput: camera.PhotoOutput | undefined = undefined;
    private session: camera.PhotoSession | camera.VideoSession | undefined = undefined;

    // 初始化相机。
    async initCamera(context: Context, surfaceId: string): Promise<void> {
    console.info(`initCamera surfaceId: ${surfaceId}`);
    try {
    await this.releaseCamera();
    // 获取相机管理器实例。
    this.cameraManager = camera.getCameraManager(context);
    if (this.cameraManager === undefined) {
    console.error('cameraManager is undefined');
    return;
    }
    this.cameras = this.cameraManager.getSupportedCameras();
    if (!this.cameras || this.cameras.length <= 0) {
    console.error("cameraManager.getSupportedCameras error");
    return;
    }
    // 创建cameraInput输出对象。
    this.cameraInput = this.cameraManager.createCameraInput(this.cameras[0]);
    if (this.cameraInput === undefined) {
    console.error('Failed to create the camera input.');
    return;
    }
    // 打开相机。
    await this.cameraInput.open();

    let previewProfile: camera.Profile = {
    format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP,
    size: {
    width: this.imageWidth,
    height: this.imageHeight
    }
    };
    // 创建预览流输出。
    this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, surfaceId);
    if (this.previewOutput === undefined) {
    console.error('Failed to create the preview stream.');
    this.releaseCamera();
    return;
    }

    let photoProfile: camera.Profile = {
    format: camera.CameraFormat.CAMERA_FORMAT_JPEG,
    size: {
    width: this.imageWidth,
    height: this.imageHeight
    }
    };
    // 创建拍照流输出。
    this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile);
    if (this.photoOutput === undefined) {
    console.error('Failed to create the photoOutput.');
    this.releaseCamera();
    return;
    }

    // 创建相机会话,启动会话。
    let session = this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO);
    if (!session) {
    console.error('session is null');
    this.releaseCamera();
    return;
    }
    this.session = session as camera.PhotoSession;
    this.session.beginConfig();
    this.session.addInput(this.cameraInput);
    this.session.addOutput(this.previewOutput);
    this.session.addOutput(this.photoOutput);
    await this.session.commitConfig();
    await this.session.start();
    } catch (error) {
    let err = error as BusinessError;
    console.error(`initCamera fail: ${err}`);
    this.releaseCamera();
    }
    }

    // 释放相机资源。
    async releaseCamera(): Promise<void> {
    console.info('releaseCamera is called');
    // 停止当前会话。
    await this.session?.stop().catch((e: BusinessError) => {console.error('Failed to stop session: ', e)});
    // 释放相机输入流。
    await this.cameraInput?.close().catch((e: BusinessError) => {console.error('Failed to close the camera: ', e)});
    // 释放预览输出流。
    await this.previewOutput?.release().catch((e: BusinessError) => {console.error('Failed to stop the preview stream: ', e)});
    // 释放拍照输出流。
    await this.photoOutput?.release().catch((e: BusinessError) => {console.error('Stop Photo Stream Failure: ', e)});
    // 释放会话。
    await this.session?.release().catch((e: BusinessError) => {console.error('Failed to release session: ', e)});
    console.info('releaseCamera success');
    }
    }
  3. 创建worker线程文件,配置worker。

    DevEco Studio支持一键生成Worker,在对应的{moduleName}目录下任意位置,点击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息,无需再手动在build-profile.json5中进行相关配置。

    CameraWorker.ets实现参考:

    let cameraService = new CameraService();
    const workerPort: ThreadWorkerGlobalScope = worker.workerPort;

    // 自定义消息格式。
    interface MessageInfo {
    hasResolve: boolean;
    type: string;
    context: Context; // 注意worker线程中无法使用getContext()直接获取宿主线程context,需要通过消息从宿主线程通信到worker线程使用。
    surfaceId: string;
    }

    workerPort.onmessage = async (e: MessageEvents) => {
    const messageInfo: MessageInfo = e.data;
    console.info(`worker onmessage type:${messageInfo.type}`)
    if ('initCamera' === messageInfo.type) {
    // 在worker线程中收到宿主线程初始化相机的消息。
    console.info(`worker initCamera surfaceId:${messageInfo.surfaceId}`)
    // 在worker线程中初始化相机。
    await cameraService.initCamera(messageInfo.context, messageInfo.surfaceId);
    } else if ('releaseCamera' === messageInfo.type) {
    // 在worker线程中收到宿主线程释放相机的消息。
    console.info('worker releaseCamera.');
    // 在worker线程中释放相机。
    await cameraService.releaseCamera();
    }
    }

    workerPort.onmessageerror = (e: MessageEvents) => {
    }

    workerPort.onerror = (e: ErrorEvent) => {
    }
  4. 创建组件,用于显示预览流,在页面相关生命周期中构造ThreadWorker实例,在worker线程中完成相机初始化和释放。

    @Entry
    @Component
    struct Index {
    private mXComponentController: XComponentController = new XComponentController();
    private surfaceId: string = '';
    @State imageWidth: number = 1920;
    @State imageHeight: number = 1080;
    // 创建ThreadWorker对象获取worker实例。
    private workerInstance: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/CameraWorker.ets');
    private uiContext: UIContext = this.getUIContext();
    private context: Context | undefined = this.uiContext.getHostContext();
    private mXComponentOptions: XComponentOptions = {
    type: XComponentType.SURFACE,
    controller: this.mXComponentController
    }

    onPageShow(): void {
    if ('' !== this.surfaceId) {
    // 通过worker实例向worker线程发送消息初始化相机。
    this.workerInstance.postMessage({
    type: 'initCamera',
    context: this.context,
    surfaceId: this.surfaceId,
    })
    }
    }

    onPageHide(): void {
    // 通过worker实例向worker线程发送消息销毁相机。
    this.workerInstance.postMessage({
    type: 'releaseCamera',
    })
    }

    build() {
    Column() {
    Column() {
    XComponent(this.mXComponentOptions)
    .onLoad(async () => {
    console.info('onLoad is called');
    // 初始化XComponent获取预览流surfaceId。
    this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
    let surfaceRect: SurfaceRect = {
    surfaceWidth: this.imageHeight,
    surfaceHeight: this.imageWidth
    };
    this.mXComponentController.setXComponentSurfaceRect(surfaceRect);
    console.info(`onLoad surfaceId: ${this.surfaceId}`);
    if (!this.workerInstance) {
    console.error('create stage worker failed');
    return;
    }
    // 宿主线程向worker线程发送初始化相机消息。
    this.workerInstance.postMessage({
    type: 'initCamera',
    context: this.context, // 将宿主线程的context传给worker线程使用。
    surfaceId: this.surfaceId, // 将surfaceId传给worker线程使用。
    })
    })// The width and height of the surface are opposite to those of the XComponent.
    .width(this.uiContext.px2vp(this.imageHeight))
    .height(this.uiContext.px2vp(this.imageWidth))

    }.justifyContent(FlexAlign.Center)
    .height('90%')

    Text('WorkerDemo')
    .fontSize(36)
    }
    .justifyContent(FlexAlign.End)
    .height('100%')
    .width('100%')
    }
    }

trace对比

不使用Worker:

使用Worker: