文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2022-06-06 13:43

关注

最近我们发现很多用户在接入虹软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 "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 将所需的动态库存放在
src/main/jniLibs
目录下

在这里插入图片描述

2.2.2 去除gradle中的Native构建配置

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

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


    //    externalNativeBuild {
    //        cmake {
    //            path "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

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯