JNI 常见异常分析

JNI基础内容:

编程语言间的互相调用可以有几种方案:

  • 通过RPC调用来实现跨语言的调用,不同语言编写的程序运行在不同的进程中,通过网络、输入输出流、管道等设施。
  • 通过共享内存实现进程间通信
  • 通过FFI(Foreign Function Interface)机制,通过提供运行时兼容的设施(一般是lib及运行时支持)实现在同一进程内跨越语言的边界调用函数,例如Java的JNI就是这样一种设施

JNI 技术背景:

运行环境:

基本数据类型:

引用数据类型:

方法签名:

Java/C 如何相互调用:

方法签名:

Jvm/JniEvn环境:

JNIEnv一般是是由虚拟机传入,而且与线程相关的变量,线程能操作的线程上下文 与 方法

JavaVM是虚拟机在JNI中的表示,一个JVM中只有一个JavaVM实例,这个实例是线程共享的

So 异常分析:

bash 复制代码
java.lang.UnsatisfiedLinkError: dlopen failed: library "/Users/user/Soft/Custom/dianxin/MH_ICT/app/src/main/cpp/lib/arm64-v8a/libcjson.so" not found: needed by /data/app/~~-fvxvo2CY1xGw_Zq_z_xjw==/com.dss.ict-tzmJw185jIwlMorqbPpxpw==/lib/arm64/libictProxy.so in namespace classloader-namespace

/Users/user/Library/Android/sdk/ndk/21.4.7075529/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin/x86_64-linux-android-readelf -d /Users/user/Downloads/app-general-debug/lib/arm64-v8a/libictProxy.so

方法异常问题 :

% objdump -T ./libonnx_decoder.so | grep "OrtGetApiBase"0000000000000000 DF UND 0000000000000000 (VERS_1.12.0) OrtGetApiBase

% objdump -T ./libonnxruntime.so | grep "OrtGetApiBase" 000000000045c51c g DF .text 000000000000000c VERS_1.19.0 OrtGetApiBase

so name 问题:

正常so

系统so 加载错误问题:

系统权限应用加载:

安卓默认会按照优先级搜索下面的路径:

  • 应用的安装目录如上面的(/data/app/xxx/lib/arm64/)
  • so文件的RPATH字段指的的目录
  • LD_LIBRARY_PATH环境变量指定的目录
  • so文件的RUNPATH字段指的的目录
  • 系统目录如/system/lib64/、/vendor/lib64/、/system/apex/com.android.i18n/lib64/等

加载后缀问题:

bash 复制代码
java.lang.UnsatisfiedLinkError: dlopen failed: library "libcurl.so.4" not found: needed by /data/app/~~gCIOmXUrd8VmBqA43J-WWw==/com.dss.ict-375hPFYMdMCuKeYl4ymVAg==/lib/arm64/libictProxy.so in namespace classloader-namespace

安卓系统是支持这种加载带版本后缀的so,但是gradle在编译apk的时候确是只会将.so后缀的文件打包到apk,所以安装之后就缺失了这个so。

在Android上库不是在系统范围内安装的它们总是应用程序包的一部分,所以so的版本标记是不必要的,谷歌就把这块在打包的时候去掉了,但这样的差异造成了在安卓上引入第三方c/c++库方面需要对so的版本号进行额外的处理。

开发过程问题

线程问题:

因为通过AttachCurrentThread附加到虚拟机 的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类

引用数量问题:

lua 复制代码
JNI ERROR (app bug): local reference table overflow (max=512)

1 在某个本地方法调用中,创建的局部引用的数量不宜过多,否则可能导致JNI内部的局部引用表溢出,应该适时释放

2 不再使用的局部引用。尽管在本地方法返回后,局部引用会被自动释放,但是在这之前若不主动释放,局部引用所引用的对象会一直存活,可能导致使用的内存在方法调用时居高不下,甚至可能出现OOM的风险。

3 当一个本地方法不再返回,也就是内部无限循环执行,此时适时释放无用局部引用是必须的。例如,一个本地方法可能进入一个无终止的事件派遣循环。释放在循环中创建的局部引用至关重要(crucial),因此不会让引用数量累增而导致内存泄露

4 局部引用引用了一个较大的对象。在本地方法调用时,可能会访问java中大对象,局部引用在被释放之前会一直阻止JVM回收这个大对象,即是之后不再使用该对象,造成了内存浪费,因此适时释放该局部引用是合理的选择。 使用NewLocalRef/ DeleteLocalRef 来使用局部引用

相关推荐
Nicholas6820 小时前
flutter视频播放器video_player_avfoundation之FVPVideoPlayer(二)
前端
文心快码BaiduComate20 小时前
一人即团队,SubAgent引爆开发者新范式
前端·后端·程序员
掘金一周20 小时前
2025年还有前端不会Nodejs ?| 掘金一周 9.25
android·前端·后端
Sailing20 小时前
前端拖拽,看似简单,其实处处是坑
前端·javascript·面试
RoyLin20 小时前
前端·后端·node.js
RoyLin20 小时前
C++ 基础与核心概念
前端·后端·node.js
记得坚持20 小时前
vue2插槽
前端·vue.js
臣臣臣臣臣什么臣20 小时前
uni-app 多文件上传:直接循环调用 uni.uploadFile 实现(并行 / 串行双模式)
android·前端
带只拖鞋去流浪21 小时前
Vue.js响应式API
前端·javascript·vue.js
Coder_R21 小时前
如何 把 Mac 上的 APK(app) 安装到安卓手机上?
前端·面试