在自定义扫码界面退到后台再回到前台时扫码停止该如何解决
===========================

#### 问题现象

Tabs组件管理的首页面是自定义扫码界面，开启相机进行扫描，然后手动切换到后台，回到前台后无法继续扫描，页面静止不动。

问题代码示例参考如下：

@Entry
@Component
struct Index {
  build() {
    Tabs({
      barPosition: BarPosition.End
    }) {
      TabContent() {
        CustomScanPage()
      }
    }
  }
}
export struct CustomScanPage {
  onPageShow(): void {
    this.customScanStart()
  }
  onPageHide(): void{
    this.releaseCamera();
  }
 _ // 初始化扫码资源_
  customScanStart() {
    customScan.init();
    customScan.start().then((scanResult: Array<scanBarcode.ScanResult\>) \=\> {
    _  // 扫码结果处理_
    }).catch((error: BusinessError) \=\> {
     _ // 错误信息处理_
    });
  }
  _// 释放扫码资源_
  releaseCamera() {
    customScan.stop();
    customScan.release();
  }
}

#### 背景知识

1.  相机切换后台再切回来，必须要重新初始化相机才可以。因为切后台，相机资源被全部回收。
2.  自定义扫码提供了扫码相机流控制接口，支持扫码界面自定义并实现基本的扫码功能。您可根据相应场景来开启、暂停和释放相机流，还可根据相应的光线条件来控制闪光灯的打开和关闭，便于用户进行扫码。

扫码时序图如下：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/20/v3/6OtglFA8QNiU973JphVNrg/zh-cn_image_0000002658791883.png?HW-CC-KV=V1&HW-CC-Date=20260709T134242Z&HW-CC-Expire=86400&HW-CC-Sign=9189336A358B55A1560670DF24BDCC6157D9E396BC5B7C763A07E6A28DBAA579 "点击放大")

详情请参考：[customScan (自定义界面扫码)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/scan-customscan-api)。

#### 问题定位

从问题代码可以看出工程中希望在onPageShow和onPageHide方法内实现扫码的初始化和释放逻辑，但是onPageShow和onPageHide方法属于页面生命周期方法，[Tabs](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-tabs)是容器组件，所管理的CustomScanPage页面属于子组件，所以不会调用页面的生命周期方法。

1.  组件调用的生命周期方法：
    *   aboutToAppear()。
    *   aboutToDisappear()。
    *   aboutToReuse()。
2.  页面调用的生命周期方法：
    *   onPageShow()。
    *   onPageHide()。
    *   onBackPress()。

#### 分析结论

通过以上分析，子组件CustomScanPage页面不会调用onPageShow和onPageHide方法，导致前后台切换时不会重新初始化和释放相机资源，再进入前台后扫码界面静止。为了实现进入后台时调用release接口进行释放扫码资源，进入前台调用init接口初始化扫码资源的目标，需要通过注册[UiObserver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-uiobserver)方法监听自定义扫码页面是否显示。

#### 修改建议

通过UiObserver方法监听自定义组件页面生命周期解决该问题。

1.  使用自定义扫码功能时，需要在module.json5中配置[ohos.permission.CAMERA](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/permissions-for-all-user#ohospermissioncamera)权限，具体申请方式请参考[声明权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/declare-permissions)。
2.  在aboutToAppear()方法中注册监听方法。
    
    async aboutToAppear() {
     _ // 添加监听_
      this.getUIContext().getUIObserver().on('routerPageUpdate', (info) \=\> {
        if (info.state \== uiObserver.RouterPageState.ON\_PAGE\_SHOW) {
          this.onPageShow();
        } else if (info.state \== uiObserver.RouterPageState.ON\_PAGE\_HIDE) {
          this.onPageHide();
        }
      });
      if (this.userGrant \=== false) {
        this.requestCameraPermission();
      }
    }
    
3.  通过监听调用的方法中实现初始化和销毁逻辑。
    
    async onPageShow() {
      if (!this.userGrant) {
      _  // 申请权限_
        this.requestCameraPermission();
      } else {
        this.customScanStart();
      }
    }
    
    async onPageHide() {
      this.releaseCamera();
    }
    
4.  在aboutToDisappear()方法中注销监听方法。
    
    async aboutToDisappear() {
     _ // 取消监听，不选择回调时，取消所有监听的回调_
      this.getUIContext().getUIObserver().off('routerPageUpdate');
    }
    

完整示例参考如下：

import { customScan, scanBarcode, scanCore } from '@kit.ScanKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { display, uiObserver } from '@kit.ArkUI';
import { abilityAccessCtrl, common, PermissionRequestResult } from '@kit.AbilityKit';

const TAG: string \= '\[Scan Sample\]';

@Entry
@Component
struct PageOne {
  build() {
    Tabs({
      barPosition: BarPosition.End
    }) {
      TabContent() {
        CustomScanCode();
      }.tabBar('首页');
    };
  }
}

@Component
export struct CustomScanCode {
  @State userGrant: boolean \= false; _// 是否已申请相机权限_
  _// 设置预览流高度，默认单位：vp_
  @State cameraHeight: number \= 640; _// 设置预览流高度，默认单位：vp_
  @State cameraWidth: number \= 360; _//_ _设置预览流宽度，默认单位：vp_
  private mXComponentController: XComponentController \= new XComponentController();
  @State isReleaseCamera: boolean \= false; _// 是否已释放相机流_
  displayHeight: number \= 0; _// 屏幕高度，单位vp_
  displayWidth: number \= 0; _// 屏幕宽度，单位vp_

  async aboutToAppear() {
   _ // 添加监听_
    this.getUIContext().getUIObserver().on('routerPageUpdate', (info) \=\> {
      if (info.state \== uiObserver.RouterPageState.ON\_PAGE\_SHOW) {
        this.onPageShow();
      } else if (info.state \== uiObserver.RouterPageState.ON\_PAGE\_HIDE) {
        this.onPageHide();
      }
    });
    if (this.userGrant \=== false) {
      this.requestCameraPermission();
    }
  }

  async aboutToDisappear() {
  _  // 取消监听，不选择回调时，取消所有监听的回调_
    this.getUIContext().getUIObserver().off('routerPageUpdate');
  }

 _ // 用户申请权限_
  async reqPermissionsFromUser(): Promise<number\[\]\> {
    hilog.info(0x0001, TAG, 'reqPermissionsFromUser start');
    let context \= this.getUIContext().getHostContext() as common.UIAbilityContext;
    let atManager \= abilityAccessCtrl.createAtManager();
    try {
      const grantStatus: PermissionRequestResult \=
        await atManager.requestPermissionsFromUser(context, \['ohos.permission.CAMERA'\]);
      return grantStatus.authResults;
    } catch (error) {
      hilog.error(0x0001, TAG, \`Failed to requestPermissionsFromUser. Code: ${error.code}, message: ${error.message}\`);
      return \[\];
    }
  }

 _ // 用户申请相机权限_
  async requestCameraPermission() {
    let grantStatus \= await this.reqPermissionsFromUser();
    for (let i \= 0; i < grantStatus.length; i++) {
      if (grantStatus\[i\] \=== 0) {
     _   // 用户授权，可以继续访问目标操作_
        hilog.info(0x0001, TAG, 'Succeeded in getting permissions.');
        this.userGrant \= true;
        break;
      }
    }
  }

  async onPageShow() {
    if (!this.userGrant) {
    _  // 申请权限_
      this.requestCameraPermission();
    } else {
      this.customScanStart();
    }
  }

  async onPageHide() {
    this.releaseCamera();
  }

 _ // 释放相机流_
  async releaseCamera() {
    if (!this.isReleaseCamera) {
      await this.stopCamera();
      await customScan.release();
      this.isReleaseCamera \= true;
    }
  }

 _ // 暂停相机流_
  async stopCamera() {
    if (!this.isReleaseCamera) {
      customScan.stop();
    }
  }

 _ // 竖屏时获取屏幕尺寸，设置预览流全屏示例_
  async setDisplay() {
    try {
   _   // 默认竖屏_
      let displayClass \= display.getDefaultDisplaySync();
      this.displayHeight \= this.getUIContext().px2vp(displayClass.height);
      this.displayWidth \= this.getUIContext().px2vp(displayClass.width);
      let maxLen: number \= Math.max(this.displayWidth, this.displayHeight);
      const RATIO: number \= 16 / 9;
      this.cameraHeight \= maxLen;
      this.cameraWidth \= maxLen / RATIO;
    } catch (error) {
      hilog.error(0x0001, TAG, \`Failed to getDefaultDisplaySync. Code: ${error.code}, message: ${error.message}\`);
    }
  }

  build() {
    Column() {
      if (this.userGrant) {
        Stack() {
          XComponent({
            id: 'componentId',
            type: XComponentType.SURFACE,
            controller: this.mXComponentController
          })
            .onLoad(() \=\> {
             _ // 获取XComponent的surfaceId_
              this.customScanStart();
            })
            .height(this.cameraHeight)
            .width(this.cameraWidth)
            .position({ x: 0, y: 0 });
        }
        .width('100%')
        .height('100%');
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }

  private customScanStart() {
    this.isReleaseCamera \= false;
    let surfaceId: string \= this.mXComponentController.getXComponentSurfaceId();
    let options: scanBarcode.ScanOptions \= {
      scanTypes: \[scanCore.ScanType.ALL\],
      enableMultiMode: true,
      enableAlbum: true
    };
    this.setDisplay();
    try {
      customScan.init(options);
    } catch (error) {
      hilog.error(0x0001, TAG, \`Failed to init customScan. Code: ${error.code}, message: ${error.message}\`);
    }
   _ // 设置ViewControl相应字段_
    let viewControl: customScan.ViewControl \= {
      width: this.cameraWidth,
      height: this.cameraHeight,
      surfaceId: surfaceId
    };
    customScan.start(viewControl).then((scanResult: Array<scanBarcode.ScanResult\>) \=\> {
      hilog.info(0x0001, '\[Scan Sample\]',
        \`Succeeded in getting ScanResult by promise, scanResult is ${JSON.stringify(scanResult)}\`);
    }).catch((error: BusinessError) \=\> {
      hilog.error(0x0001, '\[Scan Sample\]',
        \`Failed to get ScanResult by promise. Code: ${error.code}, message: ${error.message}\`);
    });
  }
}

#### 总结

1.  当前后台切换导致相机功能出现画面静止时，都可以通过每次进入后台先释放相机资源、进入前台再初始化相机资源的方案解决。
2.  Tabs组件管理的页面属于子组件，所以不会调用页面生命周期方法。