我的编程空间,编程开发者的网络收藏夹
学习永远不晚

虹软人脸识别 - Android平台调用动态库时的常见错误解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

虹软人脸识别 - Android平台调用动态库时的常见错误解析

最近我们发现很多用户在接入虹软ArcFace人脸识别SDK时,经常会遇到动态库加载失败的相关问题。本文详细介绍从编译动态库(.so)到程序调用so的整个流程,模拟在加载虹软人脸识别so文件时经常遇到的一些问题,帮助大家了解这些问题出现的原因以及解决方法。

一、 ArcFace库加载常见错误 1.1 找不到动态库
java.lang.UnsatisfiedLinkError: couldn't find "libarcsoft_face_engine.so"

原因:

在安装应用时,APK中指定的ABI目录下没有发现指定的动态库,寻找apk中动态库的规则详见

https://developer.android.google.cn/ndk/guides/abis?hl=en#aen

导致这个问题的间接原因很多,比如:

Android工程中没有指定的动态库

Android工程中动态库存放位置错误

设备支持的最高ABI是armeabi-v7a,而apk只有arm64-v8a的动态库

解决方案:

确保被安装程序中包含的目标设备支持的ABI的动态库,可以解压APK检查动态库是否存在。

1.2 加载的动态库ABI不对
java.lang.UnsatisfiedLinkError: "libarcsoft_face_engine.so" is 32-bit instead of 64-bit

原因:

在64位库目录下存放的动态库文件是32位的。

例如将armeabi-v7a的动态库存放在arm64-v8a目录下,并安装在支持arm64-v8a的设备上,就会导致这样的错误。

解决方案:

确保动态库ABI正确,一般在拷贝文件时拷贝ABI文件夹即可。

1.3 动态库文件长度为0
java.lang.UnsatisfiedLinkError: dlopen failed: file offset for the library ".../libarcsoft_face_engine.so" >= file size: 0 >= 0

原因:

动态库存在,但是文件是空的。

解决方案:

重新将动态库引入工程。

1.4 执行函数时找不到XXXX函数
java.lang.UnsatisfiedLinkError: No implementation found for int b.a.a.b.b(android.content.Context, java.lang.String, java.lang.String) (tried Java_b_a_a_b_b and Java_b_a_a_b_b__Landroid_content_Context_2Ljava_lang_String_2Ljava_lang_String_2)
        at b.a.a.b.b(Native Method)
        at b.a.a.b.a(:182)

原因:

在Java函数确定后,按照固定的规则去寻找native函数找不到。一般情况下都是Java代码混淆导致的。

解决方案:

修改混淆配置文件,确保相关的Java代码不被混淆。

1.5 在加载动态库时出现crash
JNI DETECTED ERROR IN APPLICATION: JNI RegisterNatives called with pending exception java.lang.ClassNotFoundException: Didn't find class "com.arcsoft.face.FaceEngine"

原因:

在动态库中,以指定的Java签名无法找到对应的Java类、函数、变量。

解决方案:

修改混淆配置文件,确保相关的Java代码不被混淆。

以上是常见的crash与基本原因和解决方案的介绍,接下来,我们来自己编译动态库并使用,了解下这些问题是怎么出现的。

二、自己编译并使用动态库 2.1. 编译动态库 2.1.1
CMakeLists.txt

CMakeLists.txt
里的内容比较简单,将
hello.cpp
编译成一个名为
libhello-sdk.so
的动态库

add_library(
          hello-sdk
          SHARED
          hello.cpp
)
2.1.2
hello.cpp

在这个文件中,使用JNI静态注册和动态注册的方式定义了两个函数,并在

JNI_Onload
中对需要动态注册的函数进行注册:

Java_com_arcsoft_functionregisterdemo_MainActivity_hello

需要被静态注册的函数,在Java中定义的native函数首次被调用时,会由JVM按照固定的规则去寻找native函数并注册。这个规则一般是:

Java_包名_类名_函数名
。具体的实现,大家感兴趣的话,可参考这个地址中的
JniShortName()
JniLongName()
: http://androidxref.com/9.0.0_r3/xref/art/runtime/art_method.cc

dynamicRegisterFunction

需要被动态注册的函数,一般在

JNI_OnLoad
中进行注册。

JNI_OnLoad

动态库被加载时,会被执行的函数,在这里对

dynamicRegisterFunction
进行注册。对于
JNI_OnLoad
函数被调用的具体实现,大家感兴趣的话,可参考
http://androidxref.com/9.0.0_r3/xref/art/runtime/java_vm_ext.cc 的第1009至1024行。

  #include 
  #include 
  // 静态注册的函数,对应MainActivity类中的hello函数
  extern "C" JNIEXPORT jstring JNICALL
  Java_com_arcsoft_functionregisterdemo_MainActivity_hello(JNIEnv *env,jobject thiz) {
      return env->NewStringUTF("hello world");
  }
  // 动态注册的函数
  jstring dynamicRegisterFunction(JNIEnv *env, jobject thiz) {
      return env->NewStringUTF("hello, I'm from dynamicRegisterFunction");
  }  
  // 在动态库加载时进行函数注册
  int JNI_OnLoad(JavaVM *javaVM, void*reserved) {
      JNIEnv *jniEnv; 
      if (javaVM->GetEnv((void **)(&jniEnv), JNI_VERSION_1_4) != JNI_OK) {
          return JNI_ERR;
      }
      jclass registerClass =jniEnv->FindClass("com/arcsoft/functionregisterdemo/MainActivity");
      JNINativeMethodjniNativeMethods[] = {
              // name signature nativeFunction             
             {"dynamicRegisterFunction", "()Ljava/lang/String;",
                     (void *)(dynamicRegisterFunction)}
      };
      if(jniEnv->RegisterNatives(registerClass, jniNativeMethods,
                                 sizeof(jniNativeMethods) / sizeof((jniNativeMethods)[0])) < 0) {
          return JNI_ERR;
      } else {
          return JNI_VERSION_1_4;
      }
  }
2.1.3
build.gradle

配置

CMakeLists.txt
所在路径,且配置当前编译的abi仅为
armeabi-v7a
arm64-v8a


  apply plugin: 'com.android.application'
  android {
      ...
      defaultConfig {
          ...
          ndk.abiFilters 'armeabi-v7a', 'arm64-v8a'
      }
      ...
      externalNativeBuild {
        cmake {
              path "class="lazy" data-src/main/cpp/CMakeLists.txt"
              version "3.10.2"
        }
      }
  }
  dependencies {
      ...
  }
2.1.4 编译

我们可以选择直接打包apk安装运行,但这里为了模拟调用SDK,我们可以选择手动打包动态库再拿来使用。

执行

externalNativebuild(Release|Debug)
(可
terminal
执行
gradlew externalNativebuildRelease
或点击Android
Studio右侧Gradle中的选项)编译release或debug版本的动态库,这里选择
externalNativeBuildRelease
,编译结果如下:

在这里插入图片描述
至此,

libhello-sdk.so
编译完成,接下来把工程的Native构建配置删除,像接入SDK一样使用这两个动态库。

2.2 正确地使用已编译的动态库 2.2.1 将所需的动态库存放在
class="lazy" data-src/main/jniLibs
目录下

在这里插入图片描述

2.2.2 去除gradle中的Native构建配置

由于我们已经编译好动态库了,现在去除gradle中的Native构建配置,否则会报

More than one file was found with OS independent path 'XXXX'
的错误


    //    externalNativeBuild {
    //        cmake {
    //            path "class="lazy" data-src/main/cpp/CMakeLists.txt"
    //            version "3.10.2"
    //        }
    //    }
2.2.3 在
MainActivity
中使用

  package com.arcsoft.functionregisterdemo;
  import androidx.appcompat.app.AppCompatActivity;
  import android.os.Bundle;
  import android.widget.TextView;
  public class MainActivity extends AppCompatActivity {
      // 加载动态库
      static {
         System.loadLibrary("hello-sdk");
      }
      @Override
      protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);   
         setContentView(R.layout.activity_main);
         TextView tv = findViewById(R.id.sample_text);
         tv.setText(hello());
         tv.append("\n\n");
         tv.append(dynamicRegisterFunction());
      }
      // 静态注册的函数
      public native String hello();      
      // 动态注册的函数
      public native String dynamicRegisterFunction();
  }
2.2.4 运行效果正常 在这里插入图片描述 2.3 错误地使用已编译的动态库,复现上述问题 2.3.1 找不到动态库

操作方式:jniLibs目录下不保留任何动态库

日志如下,在加载动态库时,由于在几个库目录寻找所需的动态库没找到,于是报了

UnsatisfiedLinkError
错误:
couldn't find "libhello-sdk.so"


2020-03-26 15:55:09.448 26336-26336/com.arcsoft.functionregisterdemo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.arcsoft.functionregisterdemo, PID: 26336
    java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.arcsoft.functionregisterdemo-6C3PyVyDJypXOtLP_dDykA==/base.apk"],nativeLibraryDirectories=[/data/app/com.arcsoft.functionregisterdemo 6C3PyVyDJypXOtLP_dDykA==/lib/arm64, /system/lib64, /system/vendor/lib64]]] couldn't find "libhello-sdk.so"
        at java.lang.Runtime.loadLibrary0(Runtime.java:1012)
        ....
2.3.2 加载的动态库ABI不对

操作方式:将

armeabi-v7a
的动态库放到
arm64-v8a
目录下

日志如下,在加载动态库时,虽然库是存在的,但是ABI不对,于是报了

UnsatisfiedLinkError
错误:
"XXXX" is 32-bit instead of 64-bit


  2020-03-26 15:56:25.747 26517-26517/com.arcsoft.functionregisterdemo E/AndroidRuntime: FATAL EXCEPTION:
main
      Process: com.arcsoft.functionregisterdemo, PID: 26517
      java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/com.arcsoft.functionregisterdemo-EWDPPRqzg8u7sv1Dq30ZJA==/lib/arm64/libhello-sdk.so" is 32-bit instead of 64-bit
          at java.lang.Runtime.loadLibrary0(Runtime.java:1016)
          ....
2.3.3 动态库文件长度为0

操作方式:删除动态库文件再撤销删除

这可能是Android Studio的一个小问题,有时删除文件后撤销删除,文件虽然能够重新出现,但是大小为0。

日志如下,在加载动态库时,虽然库是存在的,但是文件大小为0,于是报了

UnsatisfiedLinkError
错误:
dlopen failed: file offset for the library "XXXX" >= file size: 0 >= 0

2020-03-26 15:56:58.114
26669-26669/com.arcsoft.functionregisterdemo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.arcsoft.functionregisterdemo, PID: 26669
    java.lang.UnsatisfiedLinkError: dlopen failed: file offset for the library "/data/app/com.arcsoft.functionregisterdemo-PITl9rCd6FztSupEwwvjQA==/lib/arm64/libhello-sdk.so" >= file size: 0 >= 0
        at java.lang.Runtime.loadLibrary0(Runtime.java:1016)
        at java.lang.System.loadLibrary(System.java:1669)
        ....
2.3.4 执行函数时找不到XXXX函数

操作方式:混淆Java代码

这也是导致crash的最常见的一种场景,一般情况下,我们在编译debug版apk时,是没有进行代码混淆的,而编译release版apk时会做混淆,这就会导致debug时程序运行正常,但一运行release版就crash。刚才在代码中,我们用静态注册和动态注册两种方式实现函数的注册,

对于JNI静态注册,JVM会根据Java函数的名称和签名寻找对应的native函数,若找不到,则报

java.lang.UnsatiesFiedLinkError
错误。由于我们的动态库中包含静态注册和动态注册的函数,直接混淆所有函数可能会导致加载动态库时直接crash,因此这里手动修改静态注册的函数模拟下静态注册的函数被混淆的效果,将
hello()
函数修改为
a()
,运行,错误日志如下:

java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.arcsoft.functionregisterdemo.MainActivity.a() (tried Java_com_arcsoft_functionregisterdemo_MainActivity_a and Java_com_arcsoft_functionregisterdemo_MainActivity_a__)
              at com.arcsoft.functionregisterdemo.MainActivity.a(Native Method)

修改为原来的函数名,运行正常。

2.3.5 在加载动态库时出现crash

操作方式:混淆Java代码

混淆Java代码也可能会导致加载动态库时直接crash。

对于JNI动态注册,我们一般会在JNI_OnLoad中进行函数注册,此时native函数由函数指针确定,JVM根据指定的Java函数名和函数签名寻找对应的Java函数,若找不到,则会直接报错,错误内容一般如下:

JNI DETECTED ERROR IN APPLICATION: JNI NewGlobalRef called with pending exception java.lang.NoSuchMethodError: no static or non-static method "classSignature + . + functionName + FunctionSignature"

修改

build.gradle
文件,配置代码混淆:

buildTypes {
     debug {
         minifyEnabled true
         proguardFiles  'proguard-rules.pro'
     }
}

当前

proguard-rules.pro
中没有任何配置,因此运行直接crash,部分日志如下,从日志中可以看到,按照指定的规则寻找Java函数找不到了。

    ......
    2020-03-26 15:58:39.046 26947-26947/com.arcsoft.functionregisterdemo A/ionregisterdem: java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: JNI NewGlobalRef called with pending exception java.lang.NoSuchMethodError: no static or non-static method "Lcom/arcsoft/functionregisterdemo/MainActivity;.dynamicRegisterFunction()Ljava/lang/String;"
    ......

修改

proguard-rules.pro
,添加混淆规则,保留
MainActivity
中的native函数:

-keepclasseswithmembers class com.arcsoft.functionregisterdemo.MainActivity{
   native ;
}

此时运行效果正常,需要注意的是,如果自己编写native函数,需要在native反射修改java中的field,还需要确保需要被反射的field不被混淆。

三、 小结

若以下其中一项不满足,就无法成功调用动态库:

动态库及其依赖库存在,且加载成功

Java函数和native函数关联成功(静态注册 or 动态注册)

当遇到错误时,日志中一般都有一些关键信息,能看到错误的具体原因,我们可以分析日志,了解排错方法。


作者:qiuqyue


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

虹软人脸识别 - Android平台调用动态库时的常见错误解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

虹软人脸识别 - Android平台调用动态库时的常见错误解析

最近我们发现很多用户在接入虹软ArcFace人脸识别SDK时,经常会遇到动态库加载失败的相关问题。本文详细介绍从编译动态库(.so)到程序调用so的整个流程,模拟在加载虹软人脸识别so文件时经常遇到的一些问题,帮助大家了解这些问题出现的原因
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录