《Android原生整合虹软SDK开发uniapp插件》
2021-08-05 11:37 码仔很忙 阅读(1241) 评论(1) 编辑 收藏 举报1、项目背景
1.应公司要求,需要开发一套类似人脸打卡功能的app,但是因为我们公司没有很强的原生android开发者,所以根据现状选择了第三方跨平台的uniapp,想必目前大多人都了解这个平台了,我也就不多赘述了,直接上uniapp官方网站,它有一个缺点就是很多复杂的功能实现不了,就比如今天我们所要说的基于虹软开放平台的人脸识别功能,那么怎么办呢?当然有办法,使用android原生整合虹软SDK,然后做成插件供uniapp使用,这就是咱们今天的主题。另外具体虹软开放平台是做什么的,大家可以去官方做更深一步的了解,上官方链接:虹软官方,为什么要用虹软,多了不说,我就说一点:免费、免费、免费,这个理由怎么样?!以下是虹软开放平台提供的解决方案:
温馨提示
本篇就是针对小白写的,小白不用怕
另外也需要一定的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
}
}
}