代码改变世界

《Android原生整合虹软SDK开发uniapp插件》

2021-08-05 11:37  码仔很忙  阅读(1241)  评论(1编辑  收藏  举报

1、项目背景

1.应公司要求,需要开发一套类似人脸打卡功能的app,但是因为我们公司没有很强的原生android开发者,所以根据现状选择了第三方跨平台的uniapp,想必目前大多人都了解这个平台了,我也就不多赘述了,直接上uniapp官方网站,它有一个缺点就是很多复杂的功能实现不了,就比如今天我们所要说的基于虹软开放平台的人脸识别功能,那么怎么办呢?当然有办法,使用android原生整合虹软SDK,然后做成插件供uniapp使用,这就是咱们今天的主题。另外具体虹软开放平台是做什么的,大家可以去官方做更深一步的了解,上官方链接:虹软官方,为什么要用虹软,多了不说,我就说一点:免费、免费、免费,这个理由怎么样?!以下是虹软开放平台提供的解决方案:

微信图片_20210803110554.png

温馨提示

本篇就是针对小白写的,小白不用怕

另外也需要一定的android原生基础,入门能看懂代码即可,不需要精通

2、本篇用到的技术栈以及SDK

- 虹软人脸识别SDK v3.0

- android

- vue

- uniapp

3、技术接入部分

1、去虹软控制台(要登录哦)下载人脸识别Demo,传送阵

注意需要新建一个应用,如下图,SDK中包含Demo

在这里插入图片描述

2、 将Demo导入AndroidStudio,下图就是Demo的样子:

$\color{DarkTurquoise}{注意:AndroidStudio导入的项目路径一定不要有中文}$

*

3、如果不出意外的话,运行项目就会出现如下界面了,至此虹软Demo也就跑起来了

如果出意外了,请查看该文章的$\color{DarkTurquoise}{可能遇到的错误}$章节

4、接下来去跑uniapp的Demo,首先去uniapp官方下载Android平台uni原生插件开发Demo

5、将Demo导入AndroidStudio,下图就是Demo的样子:

$\color{DarkTurquoise}{注意:AndroidStudio导入的项目路径一定不要有中文}$

在这里插入图片描述

6、跑项目,会出现$\color{DarkTurquoise}{未配置appkey或配置错误}$字样,解决方法请参考:如何申请appkey传送阵\

$\color{DarkTurquoise}{注意解决这个问题还是稍微比较复杂点的,请认真阅读官方文档,不要怀疑官方文档的正确性}$


//过程中需要用到的一个生成 sha1 值得命令,在 C:\Program Files\Java\jre1.8.0_291\bin 路径下运行 cmd
keytool.exe -list -v -keystore 【keystore文件的绝对路径】

7、拿到 appkey 之后,写入 AndroidManifest.xml 文件中的 meta-data 中,然后将申请 appkey 过程中申请的证书配置到项目中,再次跑项目,如果不出意外的话,运行项目就会出现如下界面了,至此uniapp的Demo也就跑起来了

在这里插入图片描述

8、两个 Demo 都跑起来了,接下来就是整合两个 Demo 了,首先在 uniapp 的 Demo 中右击创建一个Module

在这里插入图片描述

9、选择 Android Library ,在右侧填写如下图几个属性,注意 Package name 尽量与虹软Demo中的一致,因为之后会避免解决一些不必要的错误,下一步

在这里插入图片描述

10、将虹软 Demo 中的如下 文件夹中的所有内容(包括文件夹)复制到刚才创建的 Module 中的同样位置

libs 
java 
jniLibs
res

11、将 Module 中的 build.gradle 中的 dependencies 全部删除,加入下面的

compileOnly fileTree(dir: '../app/libs', include: ['uniapp-v8-release.aar'])
implementation 'com.alibaba:fastjson:1.1.46.android'
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
compileOnly "com.android.support:recyclerview-v7:28.0.0"
compileOnly "com.android.support:support-v4:28.0.0"
compileOnly "com.android.support:appcompat-v7:28.0.0"
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

12、截至目前步骤,我们所有的准备基本已经就绪,接下来我们需要创建一下三个文件

FaceReco_AppProxy.java //用于初始化动态链接库
FaceReco.java //用于激活虹软SDK 
FaceRecoView.java //用于人脸检测视图

在这里插入图片描述

13、我们找到 FaceAttrPreviewActivity 文件,将关于人脸识别的核心代码拷贝到 FaceRecoView 文件中,核心代码如下:

 * 初始化引擎
 */
private void initEngine() {
    faceEngine = new FaceEngine();
    afCode = faceEngine.init(getContext(), DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(getContext()),
            16, 20, FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS);
    Log.i(TAG, "initEngine:  init: " + afCode);
    if (afCode != ErrorInfo.MOK) {
        System.out.println(R.string.init_failed+":"+afCode);
    }
}

/**
 * 卸载引擎
 */
private void unInitEngine() {
    if (afCode == 0) {
        afCode = faceEngine.unInit();
        Log.i(TAG, "unInitEngine: " + afCode);
    }
}

/**
 * 初始化摄像头
 */
private void initCamera() {
    DisplayMetrics metrics = new DisplayMetrics();
    Activity activity = (Activity)getContext();
    activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
    CameraListener cameraListener = new CameraListener() {

        @Override
        public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
            Log.i(TAG, "onCameraOpened: " + cameraId + "  " + displayOrientation + " " + isMirror);
            previewSize = camera.getParameters().getPreviewSize();
            drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation
                    , cameraId, isMirror, false, false);
        }

        @Override
        public void onPreview(byte[] nv21, Camera camera) {
            if (faceRectView != null) {
                faceRectView.clearFaceInfo();
            }
            List<FaceInfo> faceInfoList = new ArrayList<>();
            long start = System.currentTimeMillis();
            int code = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList);
            if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
                code = faceEngine.process(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList, processMask);
                if (code != ErrorInfo.MOK) {
                    return;
                }
            } else {
                return;
            }
            List<AgeInfo> ageInfoList = new ArrayList<>();
            List<GenderInfo> genderInfoList = new ArrayList<>();
            List<Face3DAngle> face3DAngleList = new ArrayList<>();
            List<LivenessInfo> faceLivenessInfoList = new ArrayList<>();
            int ageCode = faceEngine.getAge(ageInfoList);
            int genderCode = faceEngine.getGender(genderInfoList);
            int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
            int livenessCode = faceEngine.getLiveness(faceLivenessInfoList);
            // 有其中一个的错误码不为ErrorInfo.MOK,return
            if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) {
                return;
            }
            System.out.println("检测成功");
            if (faceRectView != null && drawHelper != null) {
                List<DrawInfo> drawInfoList = new ArrayList<>();
                for (int i = 0; i < faceInfoList.size(); i++) {
                    drawInfoList.add(new DrawInfo(drawHelper.adjustRect(faceInfoList.get(i).getRect()), genderInfoList.get(i).getGender(), ageInfoList.get(i).getAge(), faceLivenessInfoList.get(i).getLiveness(), RecognizeColor.COLOR_UNKNOWN, null));
                }
                drawHelper.draw(faceRectView, drawInfoList);
            }
        }

        @Override
        public void onCameraClosed() {
            Log.i(TAG, "onCameraClosed: ");
        }

        @Override
        public void onCameraError(Exception e) {
            Log.i(TAG, "onCameraError: " + e.getMessage());
        }

        @Override
        public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
            if (drawHelper != null) {
                drawHelper.setCameraDisplayOrientation(displayOrientation);
            }
            Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + "  " + displayOrientation);
        }
    };
    cameraHelper = new CameraHelper.Builder()
            .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight()))
            .rotation(activity.getWindowManager().getDefaultDisplay().getRotation())
            .specificCameraId(rgbCameraId != null ? rgbCameraId : Camera.CameraInfo.CAMERA_FACING_FRONT)
            .isMirror(false)
            .previewOn(previewView)
            .cameraListener(cameraListener)
            .build();
      cameraHelper.init();
      cameraHelper.start(); 
 }

14、此时人脸检测页面就整合到 uniapp 中了,当然还不可以使用,为什么呢?当然是还有两个文件没做完呢,一个用于激活SDK的,一个用于初始化加载动态链接库文件的,最重要的两步,开搞~\

15、首先将初始化动态链接库文件代码写入 FaceReco_AppProxy 文件中

 * 检查能否找到动态链接库,如果找不到,请修改工程配置
 *
 * @param libraries 需要的动态链接库
 * @return 动态库是否存在
 */
private boolean checkSoFile(String[] libraries,Application application) {
    ApplicationInfo applicationInfo = application.getApplicationInfo();
    File dir = new File(applicationInfo.nativeLibraryDir);
    System.out.println("文件路径:"+dir.getAbsolutePath());
    File[] files = dir.listFiles();
    if (files == null || files.length == 0) {
        return false;
    }
    List<String> libraryNameList = new ArrayList<>();
    for (File file : files) {
        System.out.println("文件名字:"+file.getName());
        libraryNameList.add(file.getName());
    }
    boolean exists = true;
    for (String library : libraries) {
        exists &= libraryNameList.contains(library);
    }
    return exists;
}


16、然后激活SDK文件代码写入 FaceReco 文件中

/**
 * 激活设备
 */
private void active(){
    Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) {
            RuntimeABI runtimeABI = FaceEngine.getRuntimeABI();
            Log.i(TAG, "subscribe: getRuntimeABI() " + runtimeABI);
            int activeCode = FaceEngine.activeOnline(mUniSDKInstance.getContext(), CommonUtil.getAppId(), CommonUtil.getSdkKey());
            emitter.onNext(activeCode);
        }
    })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<Integer>() {
                @Override
                public void onSubscribe(Disposable d) {

                }
                @Override
                public void onNext(Integer activeCode) {
                    if (activeCode == ErrorInfo.MOK) {
                        showToast(getString(R.string.active_success));
                        mJsCallback.invokeAndKeepAlive("激活成功");
                    } else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
                        showToast(getString(R.string.already_activated));
                        mJsCallback.invokeAndKeepAlive("该设备已激活");
                    } else {
                        showToast(getString(R.string.active_failed)+":"+activeCode);
                        mJsCallback.invokeAndKeepAlive("激活失败,错误码:"+activeCode);
                    }
                    ActiveFileInfo activeFileInfo = new ActiveFileInfo();
                    int res = FaceEngine.getActiveFileInfo(mUniSDKInstance.getContext(), activeFileInfo);
                    if (res == ErrorInfo.MOK) {
                        Log.i(TAG, activeFileInfo.toString());
                    }
                }
                @Override
                public void onError(Throwable e) {
                    showToast(e.getMessage());
                }
                @Override
                public void onComplete() {

                }
            });
}

17、将 AndroidManifest.xml 文件替换如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.arcsoft.arcfacedemo">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

18、至此,咱们插件的所有的配置基本完成,接下来删除两个文件夹,为什么要删除这两个文件夹呢,因为这两个文件夹都是安卓原生的 activity 视图,因为目前咱们的视图是 uniapp 来驱动的,所以用不到这些东西了

activity 
fragment

19、将 app 项目中引入咱们的插件,在 app 项目中的 build.gradle 中配置

implementation project(':arcfacedemo')

20、将项目跑起来,没有任何错误,漂亮,一切皆是那么的完美,如下图,呵呵,没有任何变化,为什么没有变化呢?咱们继续!

在这里插入图片描述

21、刚刚看到的是咱们的 uniapp 主界面,咱们目前只是把插件部分做完了,接下来就是让 uniapp 去调咱们的插件,首先去写一个界面,在这里我就不写界面了,我就直接说怎么调插件了,咦,对了,咱们的插件还没有打包,接下来打包插件

22、在 Android Studio 中选择 Build->Rebuild Project ,就将插件打包好了,如图:

在这里插入图片描述

23、怎么用呢?在这里我提供一下 package.json ,有了这个就不用我多说了吧!

{
    "name": "虹软SDK人脸检测",
    "id": "arc-face",
#### "version": "1.0.0",
    "description": "基于虹软SDK开发的人脸检测插件,插件永久维护,欢迎提需求(qq群:785919513)",
    "_dp_type":"nativeplugin",
    "_dp_nativeplugin":{
        "android": {
            "plugins": [
            	{
					"type": "module",
					"name": "arc-faceReco",
					"class": "com.arcsoft.arcfacedemo.FaceReco"
				},
				{
					"type": "component",
					"name": "arc-faceRecoView",
					"class": "com.arcsoft.arcfacedemo.FaceRecoView"
				}
            ],
            "hooksClass": "com.arcsoft.arcfacedemo.FaceReco_AppProxy",
            "integrateType": "aar",
            "abis": [
                "armeabi-v7a",
                "arm64-v8a"
            ],
            "minSdkVersion":23
        }
    }
}

24、至此插件制作的全过程讲解完毕\

25、最后附上源码:源码传送阵

4、可能遇到的错误

这个怎么说呢!一般遇到编译不通过的错误大部分都是环境问题,或者业务问题,这个需要对症下药,博主说一下自己在整合的时候遇到的一些问题吧

1.找不到动态链接库(.so文件)

解决方法:忘记把 .so 文件拷贝过来

2.忘记这个错误了,稍后补上

解决方法:创建 Module 时选择 Android Library ,而不是选择 Phone & Tablet

3.忘记这个错误了,稍后补上

解决方法:项目路径中不要有中文

5、完结

了解更多人脸识别产品相关内容请到虹软视觉开放平台