1. 介绍
本篇Codelab涉及两个HarmonyOS子系统(生物特征识别和相机),详情请参考生物特征识别和相机。本篇要介绍的是在人脸识别认证成功后,跳转到模拟相机的页面的实现方案。在这个应用中,我们通过调用相关接口,检查设备是否具有人脸识别的能力、进行人脸识别、打开相机,从而实现相关的功能。
- 🕮 说明
- 由于人脸录入不开放给三方应用调用,因此进行人脸识别前需要在手机设置中录入人脸信息。
2. 搭建HarmonyOS环境
安装DevEco Studio,详情请参考DevEco Studio下载。
设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
1.如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
2.如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 🕮 说明
- 人脸识别需要在真机上运行,因此需要提前申请证书和profile文件,详情请参考申请证书和profile。
3. 代码结构解读
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在参考中提供下载方式,接下来我们会用一小节来讲解整个工程的代码结构。
● slice:应用页面
◊ MainAbilitySlice:人脸识别的操作界面,包含校验设备是否支持人脸识别功能,人脸识别,人脸识别结果回显以及人脸识别成功后打开相机的功能。
◊ OpenCameraSlice:模拟相机的操作页面,包含打卡相机,拍照,存储相片以及切换摄像头的功能。
● util:工具类
◊ FaceAuthResult:人脸认证结果的返回码对应的常量。
◊ LogUtils:日志记录工具类。
◊ PermissionBridge:权限申请回调。
● resources:存放工程使用到的资源文件。
◊ resources\base\layout下存放xml布局文件;
◊ resources\base\media下存放图片资源。
● config.json:工程相关配置文件。
4. 页面布局
人脸识别页面
本页面主要由DirectionalLayout布局和Button、Text组件共同来构成。其中两个Button组件,作用分别为开始人脸识别和取消人脸识别;两个Text组件,作用分别为显示标题和显示返回的人脸识别结果。在resources\layout\ability_main.xml下有如下代码:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- <Text
- ohos:id="$+id:text_helloworld1"
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- ohos:left_padding="80vp"
- ohos:right_padding="80vp"
- ohos:text="生物特征识别"
- ohos:text_size="30fp"
- ohos:top_padding="100vp"
- />
- <Text
- ohos:id="$+id:text_status"
- ohos:height="100vp"
- ohos:width="match_parent"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="center"
- ohos:text_alignment="center"
- ohos:max_text_lines="3"
- ohos:multiple_lines="true"
- ohos:margin="5vp"
- ohos:text=""
- ohos:text_font="serif"
- ohos:text_size="30fp"
- ohos:top_padding="5vp"
- ohos:visibility="invisible"
- />
- <Button
- ohos:id="$+id:button_start"
- ohos:height="60vp"
- ohos:width="match_parent"
- ohos:align_parent_bottom="true"
- ohos:background_element="$graphic:button_element"
- ohos:layout_alignment="horizontal_center"
- ohos:left_padding="40vp"
- ohos:right_padding="40vp"
- ohos:text="开始人脸识别"
- ohos:text_color="#000000"
- ohos:text_size="30fp"
- ohos:left_margin="15vp"
- ohos:right_margin="15vp"
- ohos:top_margin="200vp"
- />
- <Button
- ohos:id="$+id:button_cancel"
- ohos:height="60vp"
- ohos:width="match_parent"
- ohos:align_parent_bottom="true"
- ohos:background_element="$graphic:button_element"
- ohos:layout_alignment="horizontal_center"
- ohos:left_padding="40vp"
- ohos:right_padding="40vp"
- ohos:text="取消人脸识别"
- ohos:margin="15vp"
- ohos:text_color="#000000"
- ohos:text_size="30fp"
- />
- </DirectionalLayout>
模拟相机页面
此页面主要由DirectionalLayout、DependentLayout布局和Image组件组成,其中三个Image组件作为图标,左右分别为返回、开始拍照和切换摄像头。在resources\layout\ability_open_camera.xml下有如下代码:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent">
- <DependentLayout
- ohos:id="$+id:root_container"
- ohos:height="match_parent"
- ohos:width="match_parent">
- <DirectionalLayout
- ohos:id="$+id:surface_container"
- ohos:height="match_parent"
- ohos:width="match_parent" />
- <DirectionalLayout
- ohos:width="match_parent"
- ohos:height="match_content"
- ohos:align_parent_bottom="$+id:root_container"
- ohos:bottom_margin="30vp"
- ohos:orientation="horizontal">
- <Image
- ohos:id="$+id:exit"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:weight="1"
- ohos:enabled="false"
- ohos:layout_alignment="vertical_center"
- ohos:scale_mode="center"
- ohos:image_src="$media:ic_camera_back" />
- <Image
- ohos:id="$+id:tack_picture_btn"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:weight="1"
- ohos:enabled="false"
- ohos:layout_alignment="vertical_center"
- ohos:scale_mode="center"
- ohos:image_src="$media:ic_camera_photo" />
- <Image
- ohos:id="$+id:switch_camera_btn"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:weight="1"
- ohos:enabled="false"
- ohos:layout_alignment="vertical_center"
- ohos:scale_mode="center"
- ohos:image_src="$media:ic_camera_switch" />
- </DirectionalLayout>
- </DependentLayout>
- </DirectionalLayout>
- 🕮 说明
- 布局文件中使用到的background_element样式,在entry\src\main\resources\base\graphic下有做定义,详情可以参考完整代码。
5. 相关权限
为了保证应用的成功运行,需要在config.json中声明需要如下权限:
- "reqPermissions": [
- {
- "name": "ohos.permission.ACCESS_BIOMETRIC"
- },
- {
- "name": "ohos.permission.CAMERA"
- },
- {
- "name": "ohos.permission.WRITE_USER_STORAGE"
- }
- ]
此外还需要在OpenCamera的onStart()方法中向用户申请权限,代码示例如下:
- private void requestPermission() {
- String[] permissions = {
- // 存储权限
- SystemPermission.WRITE_USER_STORAGE,
- // 相机权限
- SystemPermission.CAMERA
- };
- List<String> permissionFiltereds = Arrays.stream(permissions)
- .filter(permission -> verifySelfPermission(permission) != IBundleManager.PERMISSION_GRANTED)
- .collect(Collectors.toList());
- if (permissionFiltereds.isEmpty()) {
- PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED);
- return;
- }
- requestPermissionsFromUser(permissionFiltereds.toArray(new String[permissionFiltereds.size()]),
- PERMISSION_REQUEST_CODE);
- }
6. 人脸识别业务逻辑
在人脸识别页面(ability_main.xml)中,我们添加了开始人脸识别和取消人脸识别的Button,通过监听不同Button的点击事件,从而实现不同的业务逻辑。下面我们将分别介绍开始人脸识别和取消人脸识别的业务逻辑。
开始人脸识别业务逻辑
在开始人脸识别之前,我们需要校验当前设备(手机)是否具备人脸识别能力,代码示例如下:
- private void createStartListener() {
- // 提示用户人脸识别时将人脸对准摄像头
- getAndSetText(ResourceTable.Id_text_status, NO_FACE_RET, true);
- try {
- // 创建生物识别对象
- mBiometricAuthentication =
- BiometricAuthentication.getInstance(MainAbility.getMainAbility());
- // 检验设备是否有人脸识别功能
- int hasAuth = mBiometricAuthentication.checkAuthenticationAvailability(
- BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY,
- BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2, true);
- if (hasAuth == BiometricAuthentication.BA_CHECK_SUPPORTED) {
- // 如果支持人脸识别,则开启线程进行人脸识别
- ThreadPoolExecutor pool = new ThreadPoolExecutor(
- POOL_CORE_SIZE, POOL_MAX_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
- new LinkedBlockingQueue<>(QUEUE_SIZE), new
- ThreadPoolExecutor.DiscardOldestPolicy());
- pool.submit(runnable);
- } else {
- // 人脸识别不支持或存在其他问题 ,直接在页面显示结果,
- // 在主线程不需要通过EventHandler发送回显任务
- int retExcAuth = getRetExcAuth(hasAuth);
- getAndSetText(ResourceTable.Id_text_status, retExcAuth, true);
- }
- } catch (IllegalAccessException e) {
- LogUtils.error("createStartBtn", "IllegalAccessException when start auth");
- }
- }
- 🕮 说明
- ● checkAuthenticationAvailability方法参数说明:
- 1.BiometricAuthentication.AuthType中有三个类别,分别为
- AUTH_TYPE_BIOMETRIC_FINGERPRINT_ONLY指纹识别,AUTH_TYPE_BIOMETRIC_FACE_ONLY脸部识别以及AUTH_TYPE_BIOMETRIC_ALL指纹和面部。
- ● BiometricAuthentication.SecureLevel验证级别,3D人脸识别支持S3及以下级别的验证;2D人脸识别支持S2及以下级别的验证
由于人脸识别是耗时操作,所以这里新起了线程去做认证,代码示例如下:
- /**
- * 新建线程进行认证,避免阻塞其他任务
- */
- private Runnable runnable = new Runnable() {
- private void initHandler() {
- runner = EventRunner.getMainEventRunner();
- if (runner == null) {
- return;
- }
- myEventHandle = new MyEventHandle(runner);
- }
- @Override
- public void run() {
- // 初始化myEventHandle
- initHandler();
- // 开始认证
- startAuth();
- }
- };
开始人脸识别,代码示例如下:
- private void startAuth() {
- // retExcAuth 0认证成功 1:比对失败 2:取消认证 3:认证超时 4:打开相机失败
- // 5:busy,可能上一个认证没有结束 6:入参错误 7:人脸认证锁定(达到错误认证次数了)
- // 8:没有录入人脸 100:其他错误。
- int retExcAuth = mBiometricAuthentication.execAuthenticationAction(
- BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY,
- BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2,
- true, false, null);
- // 将认证结果发给主线程处理
- myEventHandler.sendEvent(retExcAuth);
- }
由于我们在线程中执行的人脸识别操作,需要通过EventHandler将识别结果发送到主线程中,并将识别结果显示在页面中,代码示例如下:
- /**
- * 事件分发器
- */
- private class MyEventHandle extends EventHandler {
- MyEventHandle(EventRunner runner) throws IllegalArgumentException {
- super(runner);
- }
- @Override
- protected void processEvent(InnerEvent event) {
- super.processEvent(event);
- int eventId = event.eventId;
- getAndSetText(ResourceTable.Id_text_status, eventId, true);
- }
- }
取消人脸识别
点击取消人脸识别Button,触发取消人脸识别操作,代码示例如下:
- private void createCancelBtn() {
- // 创建点击事件
- Component component = findComponentById(ResourceTable.Id_button_cancel);
- // 创建按钮
- Button cancelBtn = null;
- if (component != null && component instanceof Button) {
- cancelBtn = (Button) component;
- cancelBtn.setClickedListener(view -> {
- if (mBiometricAuthentication != null) {
- // 调用取消接口
- int result = mBiometricAuthentication.cancelAuthenticationAction();
- LogUtils.info("createCancelBtn:", result + "");
- }
- });
- }
- }
页面跳转
人脸识别成功后,跳转到模拟相机页面,代码示例如下:
- private void toAuthAfterPage() {
- Intent secondIntent = new Intent();
- // 指定待启动FA的bundleName和abilityName
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId("")
- .withBundleName(getBundleName())
- .withAbilityName(OpenCamera.class.getName())
- .build();
- secondIntent.setOperation(operation);
- // startAbility接口实现启动另一个页面
- startAbility(secondIntent);
- }
7. 相机相关业务逻辑
在模拟相机页面(ability_open_camera.xml)中,包含打开相机和切换前后置摄像头的功能,我们下面将逐一介绍。
初始化SurfaceProvider
用户授权后,开始初始化SurfaceProvider,代码示例如下:
- private void initSurface() {
- surfaceProvider = new SurfaceProvider(this);
- DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig(
- ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);
- surfaceProvider.setLayoutConfig(params);
- surfaceProvider.pinToZTop(false);
- // 添加SurfaceCallBack回调
- surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
- // 将SurfaceProvider加入到布局中
- Component component = findComponentById(ResourceTable.Id_surface_container);
- if (component instanceof ComponentContainer) {
- ((ComponentContainer) component).addComponent(surfaceProvider);
- }
- }
实现SurfaceOps.Callback回调,当Surface创建时,执行打开相机的操作,代码示例如下:
- /**
- * SurfaceCallBack,Surface回调
- */
- class SurfaceCallBack implements SurfaceOps.Callback {
- @Override
- public void surfaceCreated(SurfaceOps callbackSurfaceOps) {
- if (callbackSurfaceOps != null) {
- callbackSurfaceOps.setFixedSize(SCREEN_HEIGHT, SCREEN_WIDTH);
- }
- openCamera();
- }
- @Override
- public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {
- }
- @Override
- public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {
- }
- }
打开相机
创建surface后触发surfaceCreated回调,执行打开相机的操作。打开相机并添加相片接收的监听,代码示例如下:
- private void openCamera() {
- CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
- String[] cameraLists = cameraKit.getCameraIds();
- String cameraId = cameraLists.length > 1 && isCameraRear ? cameraLists[1] : cameraLists[0];
- CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
- cameraKit.createCamera(cameraId, cameraStateCallback, creamEventHandler);
- }
- /**
- * CameraStateCallbackImpl 相机状态回调
- */
- class CameraStateCallbackImpl extends CameraStateCallback {
- CameraStateCallbackImpl() {
- }
- @Override
- public void onCreated(Camera camera) {
- // 获取预览
- previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();
- if (previewSurface == null) {
- LogUtils.error(TAG, "create camera filed, preview surface is null");
- return;
- }
- // Wait until the preview surface is created.
- try {
- Thread.sleep(SLEEP_TIME);
- } catch (InterruptedException exception) {
- LogUtils.warn(TAG, "Waiting to be interrupted");
- }
- CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
- // 配置预览
- cameraConfigBuilder.addSurface(previewSurface);
- camera.configure(cameraConfigBuilder.build());
- cameraDevice = camera;
- enableImageGroup();
- }
- @Override
- public void onConfigured(Camera camera) {
- FrameConfig.Builder framePreviewConfigBuilder
- = camera.getFrameConfigBuilder(Camera.FrameConfigType.FRAME_CONFIG_PREVIEW);
- framePreviewConfigBuilder.addSurface(previewSurface);
- // 开启循环捕捉
- camera.triggerLoopingCapture(framePreviewConfigBuilder.build());
- }
- private void enableImageGroup() {
- if (!exitImage.isEnabled()) {
- exitImage.setEnabled(true);
- switchCameraImage.setEnabled(true);
- }
- }
- }
切换前后置摄像头
点击切换摄像头图标后,执行切换前后置摄像头操作,代码示例如下:
- private void switchClicked() {
- isCameraRear = !isCameraRear;
- openCamera();
- }
8. 效果展示
人脸识别FA(MainAbilitySlice)完成了检验设备是否支持人脸识别,人脸识别,人脸识别结果显示,成功后跳转到打开相机的FA(OpenCameraSlice);相机FA实现了相机的打开,拍照,相片存储,摄像头切换的功能。具体效果图如下:
人脸识别初始页面:
人脸识别结果显示:
相机页面:
9. 完整代码示例
编写布局与样式
1.base/graphic/background_ability_main.xml
- <?xml version="1.0" encoding="UTF-8" ?>
- <shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:shape="rectangle">
- <solid
- ohos:color="#FFFFFF"/>
- </shape>
2.base/graphic/button_element.xml
- <?xml version="1.0" encoding="utf-8"?>
- <shape
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:shape="rectangle">
- <corners
- ohos:radius="8vp"/>
- <solid
- ohos:color="#FF007DFE"/>
- </shape>
3.base/layout/ability_main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- <Text
- ohos:id="$+id:text_helloworld1"
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- ohos:left_padding="80vp"
- ohos:right_padding="80vp"
- ohos:text="生物特征识别"
- ohos:text_size="30fp"
- ohos:top_padding="100vp"
- />
- <Text
- ohos:id="$+id:text_status"
- ohos:height="100vp"
- ohos:width="match_parent"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="center"
- ohos:text_alignment="center"
- ohos:max_text_lines="3"
- ohos:multiple_lines="true"
- ohos:margin="5vp"
- ohos:text=""
- ohos:text_font="serif"
- ohos:text_size="30fp"
- ohos:top_padding="5vp"
- ohos:visibility="invisible"
- />
- <Button
- ohos:id="$+id:button_start"
- ohos:height="60vp"
- ohos:width="match_parent"
- ohos:align_parent_bottom="true"
- ohos:background_element="$graphic:button_element"
- ohos:layout_alignment="horizontal_center"
- ohos:left_padding="40vp"
- ohos:right_padding="40vp"
- ohos:text="开始人脸识别"
- ohos:text_color="#000000"
- ohos:text_size="30fp"
- ohos:left_margin="15vp"
- ohos:right_margin="15vp"
- ohos:top_margin="200vp"
- />
- <Button
- ohos:id="$+id:button_cancel"
- ohos:height="60vp"
- ohos:width="match_parent"
- ohos:align_parent_bottom="true"
- ohos:background_element="$graphic:button_element"
- ohos:layout_alignment="horizontal_center"
- ohos:left_padding="40vp"
- ohos:right_padding="40vp"
- ohos:text="取消人脸识别"
- ohos:margin="15vp"
- ohos:text_color="#000000"
- ohos:text_size="30fp"
- />
- </DirectionalLayout>
4.base/layout/ability_open_camera.xml
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent">
- <DependentLayout
- ohos:id="$+id:root_container"
- ohos:height="match_parent"
- ohos:width="match_parent">
- <DirectionalLayout
- ohos:id="$+id:surface_container"
- ohos:height="match_parent"
- ohos:width="match_parent" />
- <DirectionalLayout
- ohos:width="match_parent"
- ohos:height="match_content"
- ohos:align_parent_bottom="$+id:root_container"
- ohos:bottom_margin="30vp"
- ohos:orientation="horizontal">
- <Image
- ohos:id="$+id:exit"
- ohos:height="170px"
- ohos:width="match_parent"
- ohos:weight="1"
- ohos:enabled="false"
- ohos:layout_alignment="vertical_center"
- ohos:scale_mode="center"
- ohos:image_src="$media:ic_camera_back" />
- <Image
- ohos:id="$+id:tack_picture_btn"
- ohos:height="170px"
- ohos:width="match_parent"
- ohos:weight="1"
- ohos:enabled="false"
- ohos:layout_alignment="vertical_center"
- ohos:scale_mode="center"
- ohos:image_src="$media:ic_camera_photo" />
- <Image
- ohos:id="$+id:switch_camera_btn"
- ohos:height="170px"
- ohos:width="match_parent"
- ohos:weight="1"
- ohos:enabled="false"
- ohos:layout_alignment="vertical_center"
- ohos:scale_mode="zoom_center"
- ohos:image_src="$media:ic_camera_switch" />
- </DirectionalLayout>
- </DependentLayout>
- </DirectionalLayout>
功能逻辑代码
1.com/huawei/cookbook/slice/MainAbilitySlice.java
- package com.huawei.cookbook.slice;
- import com.huawei.cookbook.MainAbility;
- import com.huawei.cookbook.OpenCamera;
- import com.huawei.cookbook.ResourceTable;
- import com.huawei.cookbook.util.FaceAuthResult;
- import com.huawei.cookbook.util.LogUtils;
- import ohos.aafwk.ability.AbilitySlice;
- import ohos.aafwk.content.Intent;
- import ohos.aafwk.content.Operation;
- import ohos.agp.components.Button;
- import ohos.agp.components.Component;
- import ohos.agp.components.Text;
- import ohos.agp.utils.Color;
- import ohos.biometrics.authentication.BiometricAuthentication;
- import ohos.eventhandler.EventHandler;
- import ohos.eventhandler.EventRunner;
- import ohos.eventhandler.InnerEvent;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- /**
- * MainAbilitySlice
- *
- * @since 2021-04-12
- */
- public class MainAbilitySlice extends AbilitySlice {
- private static final int POOL_CORE_SIZE = 2;
- private static final int POOL_MAX_SIZE = 5;
- private static final int NO_FACE_RET = -1;
- private static final int KEEP_ALIVE_TIME = 3;
- private static final int QUEUE_SIZE = 6;
- private static final int RET_NOT_SUPPORTED = 1;
- private static final int RET_SAFE_LEVEL_NOT_SUPPORTED = 2;
- private static final int RET_NOT_LOCAL = 3;
- private EventRunner runner;
- private MyEventHandle myEventHandle;
- private BiometricAuthentication mBiometricAuthentication;
- /**
- * 新建线程进行认证,避免阻塞其他任务
- */
- private Runnable runnable = new Runnable() {
- private void initHandler() {
- runner = EventRunner.getMainEventRunner();
- if (runner == null) {
- return;
- }
- myEventHandle = new MyEventHandle(runner);
- }
- @Override
- public void run() {
- // 初始化myEventHandle
- initHandler();
- // 开始认证
- startAuth();
- }
- };
- /**
- * onStart
- *
- * @param intent intent
- */
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_main);
- // 创建开始认证按钮,并添加点击事件
- createStartBtn();
- // 创建取消认证按钮,并添加点击事件
- createCancelBtn();
- }
- /**
- * 创建取消按钮
- */
- private void createCancelBtn() {
- // 创建点击事件
- Component component = findComponentById(ResourceTable.Id_button_cancel);
- // 创建按钮
- Button cancelBtn = null;
- if (component != null && component instanceof Button) {
- cancelBtn = (Button) component;
- cancelBtn.setClickedListener(view -> {
- if (mBiometricAuthentication != null) {
- // 调用取消接口
- int result = mBiometricAuthentication.cancelAuthenticationAction();
- LogUtils.info("createCancelBtn:", result + "");
- }
- });
- }
- }
- /**
- * 创建开始识别的按钮点击事件
- */
- private void createStartBtn() {
- // 创建点击事件
- Component component = findComponentById(ResourceTable.Id_button_start);
- // 创建按钮
- Button featureBtn = null;
- if (component != null && component instanceof Button) {
- featureBtn = (Button) component;
- featureBtn.setClickedListener(view -> {
- createStartListener();
- });
- }
- }
- private void createStartListener() {
- // 提示用户人脸识别时将人脸对准摄像头
- getAndSetText(ResourceTable.Id_text_status, NO_FACE_RET, true);
- try {
- // 创建生物识别对象
- mBiometricAuthentication = BiometricAuthentication.getInstance(MainAbility.getMainAbility());
- // 检验设备是否有人脸识别功能
- // BiometricAuthentication.AuthType中有三个类别
- // 分别为AUTH_TYPE_BIOMETRIC_FINGERPRINT_ONLY指纹识别
- // AUTH_TYPE_BIOMETRIC_FACE_ONLY脸部识别
- // AUTH_TYPE_BIOMETRIC_ALL指纹和面部
- // BiometricAuthentication.SecureLevel 2D人脸识别建议使用SECURE_LEVEL_S2,3D人脸识别建议使用SECURE_LEVEL_S3
- int hasAuth = mBiometricAuthentication.checkAuthenticationAvailability(
- BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY,
- BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2, true);
- // hasAuth 0是支持,1是不支持,2安全级别不支持 3不是本地认证 4无人脸录入
- if (hasAuth == 0) {
- ThreadPoolExecutor pool = new ThreadPoolExecutor(
- POOL_CORE_SIZE, POOL_MAX_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
- new LinkedBlockingQueue<>(QUEUE_SIZE), new ThreadPoolExecutor.DiscardOldestPolicy());
- pool.submit(runnable);
- } else {
- // 人脸识别不支持或存在其他问题 ,直接回显页面,
- // 在主线程不需要通过EventHandler发送回显任务
- int retExcAuth = getRetExcAuth(hasAuth);
- getAndSetText(ResourceTable.Id_text_status, retExcAuth, true);
- }
- } catch (IllegalAccessException e) {
- LogUtils.error("createStartBtn", "IllegalAccessException when start auth");
- }
- }
- /**
- * 开始认证
- */
- private void startAuth() {
- // retExcAuth 0认证成功 1:比对失败 2:取消认证 3认证超时 4:打开相机失败
- // 5:busy,可能上一个认证没有结束 6:入参错误 7:人脸认证锁定(达到错误认证次数了)
- // 8:没有录入人脸 100:其他错误。
- int retExcAuth = mBiometricAuthentication.execAuthenticationAction(
- BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY,
- BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2,
- true, false, null);
- // 将修改页面发送到主线程执行
- myEventHandle.sendEvent(retExcAuth);
- }
- /**
- * 根据检验是否支持认证返回值获取提示code
- *
- * @param hasAuth 是否有认证能力
- * @return 返回认证码
- */
- private int getRetExcAuth(int hasAuth) {
- int retExcAuth;
- if (hasAuth == RET_NOT_SUPPORTED) {
- // 1是不支持2D人脸识别
- retExcAuth = FaceAuthResult.AUTH_2D_NOT_SUPPORTED;
- } else if (hasAuth == RET_SAFE_LEVEL_NOT_SUPPORTED) {
- // 安全级别不支持
- retExcAuth = FaceAuthResult.AUTH_SAFE_LEVEL_NOT_SUPPORTED;
- } else if (hasAuth == RET_NOT_LOCAL) {
- // 是不是本地认证
- retExcAuth = FaceAuthResult.AUTH_NOT_LOCAL;
- } else {
- // 无人脸录入
- retExcAuth = FaceAuthResult.AUTH_NO_FACE;
- }
- return retExcAuth;
- }
- /**
- * 获取并设置text
- *
- * @param textId 文本框id
- * @param retExcAuth 认证返回码
- * @param isVisible 是否显示
- */
- private void getAndSetText(int textId, int retExcAuth, boolean isVisible) {
- // 获取状态Text
- Component componentText = findComponentById(textId);
- if (componentText != null && componentText instanceof Text) {
- Text text = (Text) componentText;
- setTextValueAndColor(retExcAuth, text);
- if (isVisible) {
- text.setVisibility(Component.VISIBLE);
- }
- }
- }
- /**
- * 设置文本提示信息
- *
- * @param text 文本对象
- * @param textValue 文本值
- * @param color 文本颜色
- */
- private void setTextValueAndColor(Text text, String textValue, Color color) {
- text.setText(textValue);
- text.setTextColor(color);
- }
- /**
- * 设置文本显示值和文本颜色
- *
- * @param retExcAuth 认证返回值
- * @param text 文本对象
- */
- private void setTextValueAndColor(int retExcAuth, Text text) {
- switch (retExcAuth) {
- case FaceAuthResult.AUTH_SUCCESS:
- setTextValueAndColor(text, "认证成功", Color.GREEN);
- // 页面跳转
- toAuthAfterPage();
- break;
- case FaceAuthResult.AUTH_FAIL:
- setTextValueAndColor(text, "比对失败", Color.RED);
- break;
- case FaceAuthResult.AUTH_CANCLE:
- setTextValueAndColor(text, "取消认证", Color.RED);
- break;
- case FaceAuthResult.AUTH_TIME_OUT:
- setTextValueAndColor(text, "认证超时", Color.RED);
- break;
- case FaceAuthResult.AUTH_OPEN_CAMERA_FAIL:
- setTextValueAndColor(text, "打开相机失败", Color.RED);
- break;
- case FaceAuthResult.AUTH_BUSY:
- setTextValueAndColor(text, "busy,可能上一个认证没有结束", Color.RED);
- break;
- case FaceAuthResult.AUTH_PARAM_ERROR:
- setTextValueAndColor(text, "入参错误", Color.RED);
- break;
- case FaceAuthResult.AUTH_FACE_LOCKED:
- setTextValueAndColor(text, "人脸认证锁定(达到错误认证次数了)", Color.RED);
- break;
- case FaceAuthResult.AUTH_NO_FACE:
- setTextValueAndColor(text, "无人脸录入,请录入人脸。", Color.BLUE);
- break;
- case FaceAuthResult.AUTH_OTHER_ERROR:
- setTextValueAndColor(text, "其他错误。", Color.RED);
- break;
- case FaceAuthResult.AUTH_2D_NOT_SUPPORTED:
- setTextValueAndColor(text, "不支持2D人脸识别。", Color.BLUE);
- break;
- case FaceAuthResult.AUTH_SAFE_LEVEL_NOT_SUPPORTED:
- setTextValueAndColor(text, "安全级别不支持。", Color.BLUE);
- break;
- case FaceAuthResult.AUTH_NOT_LOCAL:
- setTextValueAndColor(text, "不是本地认证。", Color.BLUE);
- break;
- default:
- setTextValueAndColor(text, "开始认证,请将视线对准摄像头。。。。。。。", Color.BLUE);
- break;
- }
- }
- private void toAuthAfterPage() {
- Intent secondIntent = new Intent();
- // 指定待启动FA的bundleName和abilityName
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId("")
- .withBundleName(getBundleName())
- .withAbilityName(OpenCamera.class.getName())
- .build();
- secondIntent.setOperation(operation);
- // 通过AbilitySlice的startAbility接口实现启动另一个页面
- startAbility(secondIntent);
- }
- /**
- * 事件分发器
- *
- * @since 2021-04-12
- */
- private class MyEventHandle extends EventHandler {
- MyEventHandle(EventRunner runner) throws IllegalArgumentException {
- super(runner);
- }
- @Override
- protected void processEvent(InnerEvent event) {
- super.processEvent(event);
- int eventId = event.eventId;
- getAndSetText(ResourceTable.Id_text_status, eventId, true);
- }
- }
- @Override
- public void onStop() {
- mBiometricAuthentication.cancelAuthenticationAction();
- BiometricAuthentication.AuthenticationTips authenticationTips
- = mBiometricAuthentication.getAuthenticationTips();
- String tips = authenticationTips.tipInfo;
- }
- }
2.com/huawei/cookbook/slice/OpenCameraSlice.java
- package com.huawei.cookbook.slice;
- import com.huawei.cookbook.ResourceTable;
- import com.huawei.cookbook.util.LogUtils;
- import com.huawei.cookbook.util.PermissionBridge;
- import ohos.aafwk.ability.AbilitySlice;
- import ohos.aafwk.content.Intent;
- import ohos.agp.components.Component;
- import ohos.agp.components.ComponentContainer;
- import ohos.agp.components.DirectionalLayout;
- import ohos.agp.components.Image;
- import ohos.agp.components.surfaceprovider.SurfaceProvider;
- import ohos.agp.graphics.Surface;
- import ohos.agp.graphics.SurfaceOps;
- import ohos.agp.window.dialog.ToastDialog;
- import ohos.app.Context;
- import ohos.eventhandler.EventHandler;
- import ohos.eventhandler.EventRunner;
- import ohos.media.camera.CameraKit;
- import ohos.media.camera.device.Camera;
- import ohos.media.camera.device.CameraConfig;
- import ohos.media.camera.device.CameraStateCallback;
- import ohos.media.camera.device.FrameConfig;
- /**
- * 打开相机slice
- */
- public class OpenCameraSlice extends AbilitySlice implements PermissionBridge.OnPermissionStateListener {
- private static final String TAG = OpenCameraSlice.class.getName();
- private static final int SCREEN_WIDTH = 1080;
- private static final int SCREEN_HEIGHT = 1920;
- private static final int SLEEP_TIME = 200;
- private EventHandler creamEventHandler;
- private Image exitImage;
- private SurfaceProvider surfaceProvider;
- private Image switchCameraImage;
- private boolean isCameraRear;
- private Camera cameraDevice;
- private Surface previewSurface;
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_open_camera);
- new PermissionBridge().setOnPermissionStateListener(this);
- }
- private void initSurface() {
- surfaceProvider = new SurfaceProvider(this);
- DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig(
- ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);
- surfaceProvider.setLayoutConfig(params);
- surfaceProvider.pinToZTop(false);
- // 添加SurfaceCallBack回调
- surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
- // 将SurfaceProvider加入到布局中
- Component component = findComponentById(ResourceTable.Id_surface_container);
- if (component instanceof ComponentContainer) {
- ((ComponentContainer) component).addComponent(surfaceProvider);
- }
- }
- private void initControlComponents() {
- // 退出拍照页面图标
- Component exitImageCom = findComponentById(ResourceTable.Id_exit);
- if (exitImageCom instanceof Image) {
- exitImage = (Image) exitImageCom;
- exitImage.setClickedListener(component -> terminate());
- }
- // 切换前后置摄像头图标
- Component switchCameraImageCom = findComponentById(ResourceTable.Id_switch_camera_btn);
- if (switchCameraImageCom instanceof Image) {
- switchCameraImage = (Image) switchCameraImageCom;
- switchCameraImage.setClickedListener(component -> switchClicked());
- }
- }
- private void switchClicked() {
- isCameraRear = !isCameraRear;
- openCamera();
- }
- private void openCamera() {
- CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
- String[] cameraLists = cameraKit.getCameraIds();
- String cameraId = cameraLists.length > 1 && isCameraRear ? cameraLists[1] : cameraLists[0];
- CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
- cameraKit.createCamera(cameraId, cameraStateCallback, creamEventHandler);
- }
- private void showTips(Context context, String message) {
- getUITaskDispatcher().asyncDispatch(() -> {
- ToastDialog toastDialog = new ToastDialog(context);
- toastDialog.setAutoClosable(false);
- toastDialog.setContentText(message);
- toastDialog.show();
- });
- }
- @Override
- public void onPermissionGranted() {
- getWindow().setTransparent(true);
- initSurface();
- initControlComponents();
- creamEventHandler = new EventHandler(EventRunner.create("======CameraBackground"));
- }
- @Override
- public void onPermissionDenied() {
- showTips(OpenCameraSlice.this, "=======No permission");
- }
- /**
- * CameraStateCallbackImpl
- */
- class CameraStateCallbackImpl extends CameraStateCallback {
- CameraStateCallbackImpl() {
- }
- @Override
- public void onCreated(Camera camera) {
- // 获取预览
- previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();
- if (previewSurface == null) {
- LogUtils.error(TAG, "create camera filed, preview surface is null");
- return;
- }
- // Wait until the preview surface is created.
- try {
- Thread.sleep(SLEEP_TIME);
- } catch (InterruptedException exception) {
- LogUtils.warn(TAG, "Waiting to be interrupted");
- }
- CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
- // 配置预览
- cameraConfigBuilder.addSurface(previewSurface);
- camera.configure(cameraConfigBuilder.build());
- cameraDevice = camera;
- enableImageGroup();
- }
- @Override
- public void onConfigured(Camera camera) {
- FrameConfig.Builder framePreviewConfigBuilder
- = camera.getFrameConfigBuilder(Camera.FrameConfigType.FRAME_CONFIG_PREVIEW);
- framePreviewConfigBuilder.addSurface(previewSurface);
- // 开启循环捕捉
- camera.triggerLoopingCapture(framePreviewConfigBuilder.build());
- }
- private void enableImageGroup() {
- if (!exitImage.isEnabled()) {
- exitImage.setEnabled(true);
- switchCameraImage.setEnabled(true);
- }
- }
- }
- /**
- * SurfaceCallBack
- */
- class SurfaceCallBack implements SurfaceOps.Callback {
- @Override
- public void surfaceCreated(SurfaceOps callbackSurfaceOps) {
- if (callbackSurfaceOps != null) {
- callbackSurfaceOps.setFixedSize(SCREEN_HEIGHT, SCREEN_WIDTH);
- }
- openCamera();
- }
- @Override
- public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {
- }
- @Override
- public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {
- }
- }
- @Override
- public void onStop() {
- cameraDevice.release();
- }
- }
3.com/huawei/cookbook/util/FaceAuthResult.java
- package com.huawei.cookbook.util;
- /**
- * 人脸认证返回码
- *
- * @since 2021-04-12
- */
- public class FaceAuthResult {
- /**
- * 认证成功
- */
- public static final int AUTH_SUCCESS = 0;
- /**
- * 认证失败
- */
- public static final int AUTH_FAIL = 1;
- /**
- * 取消认证
- */
- public static final int AUTH_CANCLE = 2;
- /**
- * 认证超时
- */
- public static final int AUTH_TIME_OUT = 3;
- /**
- * 打开相机失败
- */
- public static final int AUTH_OPEN_CAMERA_FAIL = 4;
- /**
- * busy,可能上一个认证没有结束
- */
- public static final int AUTH_BUSY = 5;
- /**
- * 入参错误
- */
- public static final int AUTH_PARAM_ERROR = 6;
- /**
- * 人脸认证锁定(达到错误认证次数了)
- */
- public static final int AUTH_FACE_LOCKED = 7;
- /**
- * 没有录入人脸
- */
- public static final int AUTH_NO_FACE = 8;
- /**
- * 不支持2D人脸识别。
- */
- public static final int AUTH_2D_NOT_SUPPORTED = 9;
- /**
- * 安全级别不支持
- */
- public static final int AUTH_SAFE_LEVEL_NOT_SUPPORTED = 10;
- /**
- * 不是本地认证
- */
- public static final int AUTH_NOT_LOCAL = 11;
- /**
- * 其他问题
- */
- public static final int AUTH_OTHER_ERROR = 100;
- private FaceAuthResult() {
- super();
- }
- }
4.com/huawei/cookbook/util/LogUtils.java
- package com.huawei.cookbook.util;
- import ohos.hiviewdfx.HiLog;
- import ohos.hiviewdfx.HiLogLabel;
- /**
- * LogUtils
- *
- * @since 2021-04-12
- */
- public class LogUtils {
- private static final String TAG_LOG = "LogUtil";
- private static final HiLogLabel LABEL_LOG = new HiLogLabel(0, 0, LogUtils.TAG_LOG);
- private static final String LOG_FORMAT = "%{public}s: %{public}s";
- private LogUtils() {
- }
- /**
- * Print debug log
- *
- * @param tag log tag
- * @param msg log message
- */
- public static void debug(String tag, String msg) {
- HiLog.debug(LABEL_LOG, LOG_FORMAT, tag, msg);
- }
- /**
- * Print info log
- *
- * @param tag log tag
- * @param msg log message
- */
- public static void info(String tag, String msg) {
- HiLog.info(LABEL_LOG, LOG_FORMAT, tag, msg);
- }
- /**
- * Print warn log
- *
- * @param tag log tag
- * @param msg log message
- */
- public static void warn(String tag, String msg) {
- HiLog.warn(LABEL_LOG, LOG_FORMAT, tag, msg);
- }
- /**
- * Print error log
- *
- * @param tag log tag
- * @param msg log message
- */
- public static void error(String tag, String msg) {
- HiLog.error(LABEL_LOG, LOG_FORMAT, tag, msg);
- }
- }
5.com/huawei/cookbook/util/PermissionBridge.java
- package com.huawei.cookbook.util;
- import ohos.eventhandler.EventHandler;
- import ohos.eventhandler.EventRunner;
- import ohos.eventhandler.InnerEvent;
- /**
- * PermissionBridge
- *
- * @since 2021-04-12
- */
- public class PermissionBridge {
- /**
- * permission handler granted
- */
- public static final int EVENT_PERMISSION_GRANTED = 0x0000023;
- /**
- * permission handler denied
- */
- public static final int EVENT_PERMISSION_DENIED = 0x0000024;
- private static final String TAG = PermissionBridge.class.getSimpleName();
- private static OnPermissionStateListener onPermissionStateListener;
- private static EventHandler handler = new EventHandler(EventRunner.current()) {
- @Override
- protected void processEvent(InnerEvent event) {
- switch (event.eventId) {
- case EVENT_PERMISSION_GRANTED:
- onPermissionStateListener.onPermissionGranted();
- break;
- case EVENT_PERMISSION_DENIED:
- onPermissionStateListener.onPermissionDenied();
- break;
- default:
- LogUtils.info(TAG, "EventHandler Undefined Event");
- break;
- }
- }
- };
- /**
- * setOnPermissionStateListener
- *
- * @param permissionStateListener OnPermissionStateListener
- */
- public void setOnPermissionStateListener(OnPermissionStateListener permissionStateListener) {
- onPermissionStateListener = permissionStateListener;
- }
- /**
- * OnPermissionStateListener
- *
- * @since 2021-04-12
- */
- public interface OnPermissionStateListener {
- /**
- * 当授权时
- */
- void onPermissionGranted();
- /**
- * 当拒绝授权时触发
- */
- void onPermissionDenied();
- }
- /**
- * getHandler
- *
- * @return EventHandler
- */
- public static EventHandler getHandler() {
- return handler;
- }
- }
6.com/huawei/cookbook/MainAbility.java
- package com.huawei.cookbook;
- import com.huawei.cookbook.slice.MainAbilitySlice;
- import ohos.aafwk.ability.Ability;
- import ohos.aafwk.ability.IDataAbilityObserver;
- import ohos.aafwk.content.Intent;
- /**
- * MainAbility
- *
- * @since 2021-04-12
- */
- public class MainAbility extends Ability {
- /**
- * 声明静态变量,用于获取生物识别对象
- */
- private static MainAbility myAbility;
- /**
- * 私有构造
- */
- public MainAbility() {
- myAbility = this;
- }
- /**
- * 获取ability
- *
- * @return MainAbility
- */
- public static MainAbility getMainAbility() {
- return myAbility;
- }
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setMainRoute(MainAbilitySlice.class.getName());
- }
- }
7.com/huawei/cookbook/MyApplication.java
- package com.huawei.cookbook;
- import ohos.aafwk.ability.AbilityPackage;
- /**
- * MyApplication
- *
- * @since 2021-04-12
- */
- public class MyApplication extends AbilityPackage {
- @Override
- public void onInitialize() {
- super.onInitialize();
- }
- }
8.com/huawei/cookbook/OpenCamera.java
- package com.huawei.cookbook;
- import com.huawei.cookbook.slice.OpenCameraSlice;
- import com.huawei.cookbook.util.PermissionBridge;
- import ohos.aafwk.ability.Ability;
- import ohos.aafwk.content.Intent;
- import ohos.bundle.IBundleManager;
- import ohos.security.SystemPermission;
- import java.util.Arrays;
- import java.util.List;
- import java.util.stream.Collectors;
- /**
- * 打开相机ability
- *
- * @since 2021-04-12
- */
- public class OpenCamera extends Ability {
- /**
- * permission handler granted
- */
- private static final int EVENT_PERMISSION_GRANTED = 0x0000023;
- /**
- * permission handler denied
- */
- private static final int EVENT_PERMISSION_DENIED = 0x0000024;
- private static final int PERMISSION_REQUEST_CODE = 0;
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setMainRoute(OpenCameraSlice.class.getName());
- requestPermission();
- }
- private void requestPermission() {
- String[] permissions = {
- // 存储权限
- SystemPermission.WRITE_USER_STORAGE,
- // 相机权限
- SystemPermission.CAMERA
- };
- List permissionFiltereds = Arrays.stream(permissions)
- .filter(permission -> verifySelfPermission(permission) != IBundleManager.PERMISSION_GRANTED)
- .collect(Collectors.toList());
- if (permissionFiltereds.isEmpty()) {
- PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED);
- return;
- }
- requestPermissionsFromUser(permissionFiltereds.toArray(new String[permissionFiltereds.size()]),
- PERMISSION_REQUEST_CODE);
- }
- @Override
- public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
- if (permissions == null || permissions.length == 0 || grantResults == null || grantResults.length == 0) {
- return;
- }
- for (int grantResult : grantResults) {
- if (grantResult != IBundleManager.PERMISSION_GRANTED) {
- PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_DENIED);
- terminateAbility();
- return;
- }
- }
- PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED);
- }
- }
9.打开相机页面图标
返回图标:
拍照图标:
切换前后置摄像头图标:
10. 恭喜您
恭喜你已经完成了基于面容识别的HarmonyOS应用开发的Codelab,并且学到了:
1.人脸识别的开发。
2.相机的打开。
3.线程间通信开发EventHandler。