深入浅出Android NDK之java.lang.UnsatisfiedLinkError

目录
上一篇 深入浅出Android NDK之Hello-JNI

我们修改一下上一章的例子将com_example_hello_jni_Test.cpp的第一行注释。

//#include "com_example_hello_jni_Test.h"
#include <jni.h>
JNIEXPORT jint JNICALL Java_com_example_hello_1jni_Test_connectV1
  (JNIEnv *env, jobject thiz, jbyte p1, jchar p2, jshort p3, jint p4, jlong p5, jstring p6, jobject p7) {
    return 1;
  }

JNIEXPORT jint JNICALL Java_com_example_hello_1jni_Test_connectV2
  (JNIEnv *env, jclass cls, jbyte p1, jchar p2, jshort p3, jint p4, jlong p5, jstring p6, jobject p7) {
     return 2;
  }

再运行时,会报一个错:

2019-10-14 16:05:55.112 3811-3811/com.example.hello_jni E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.hello_jni, PID: 3811
    java.lang.UnsatisfiedLinkError: No implementation found for int com.example.hello_jni.Test.connectV1(byte, char, short, int, long, java.lang.String, android.graphics.Bitmap) (tried Java_com_example_hello_1jni_Test_connectV1 and Java_com_example_hello_1jni_Test_connectV1__BCSIJLjava_lang_String_2Landroid_graphics_Bitmap_2)
        at com.example.hello_jni.Test.connectV1(Native Method)
        at com.example.hello_jni.MainActivity.onCreate(MainActivity.java:15)
        at android.app.Activity.performCreate(Activity.java:6700)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2691)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2803)
        at android.app.ActivityThread.-wrap12(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1541)
        at android.os.Handler.dispatchMessage(Handler.java:110)
        at android.os.Looper.loop(Looper.java:203)
        at android.app.ActivityThread.main(ActivityThread.java:6265)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
2019-10-14 16:05:55.120 3811-3811/com.example.hello_jni I/Process: Sending signal. PID: 3811 SIG: 9

调用test.connectV1函数时发生了java.lang.UnsatisfiedLinkError异常,为什么会这样呢?
我们再做个实验,把com_example_hello_jni_Test.cpp重命名为com_example_hello_jni_Test.c,再编译运行,这下竟然成功了,竟然没有抛异常。
为什么会这样?我们查看com_example_hello_jni_Test.h你会发现在他的开头有

#ifdef __cplusplus
extern "C" {
#endif
/*

也就是说,C++源文件的jni函数前面必须加上extern “C”关键字,不然会抛java.lang.UnsatisfiedLinkError异常。
下面我们把com_example_hello_jni_Test.c重新修改为com_example_hello_jni_Test.cpp。然后修改内容如下:

//#include "com_example_hello_jni_Test.h"
#include <jni.h>
extern "C" JNIEXPORT jint JNICALL Java_com_example_hello_1jni_Test_connectV1
  (JNIEnv *env, jobject thiz, jbyte p1, jchar p2, jshort p3, jint p4, jlong p5, jstring p6, jobject p7) {
    return 1;
  }

extern "C" JNIEXPORT jint JNICALL Java_com_example_hello_1jni_Test_connectV2
  (JNIEnv *env, jclass cls, jbyte p1, jchar p2, jshort p3, jint p4, jlong p5, jstring p6, jobject p7) {
     return 2;
  }

运行成功, 这说明,跟包不包含com_example_hello_jni_Test.h头文件没关系,只要函数以extern "C"声明就可以。

为什么不extern "C"就会抛java.lang.UnsatisfiedLinkError?能不能链接上函数,主要在于SO的符号表中能不能查找得到相应的函数。
我们运行一下nm命令打印一下libhello-jni.so的符号表:

C:\Users\zy>D:\android-ndk-r20b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-nm -D D:\ndk-demo\hello-jni\app\build\intermediates\stripped_native_libs\debug\out\lib\armeabi-v7a\libhello-jni.so

在输出中我们能看到以下内容:

00000ebc T Java_com_example_hello_1jni_Test_connectV1
00000f18 T Java_com_example_hello_1jni_Test_connectV2

下面我们去掉extern "C"运行一下,让其抛出java.lang.UnsatisfiedLinkError异常,这时我们再运行一下nm命令,得到的输出如下:

00000f14 T _Z42Java_com_example_hello_1jni_Test_connectV1P7_JNIEnvP8_jobjectatsixP8_jstringS2_
00000f70 T _Z42Java_com_example_hello_1jni_Test_connectV2P7_JNIEnvP7_jclassatsixP8_jstringP8_jobject

看到区别了吗?去掉extern "C"后,函数名称后面变了,这是因为C++为了支持函数重载,所以会将函数的参数进行编码附加上函数名称的背后,所以C++的jni函数前面一定要加上extern "C"关键字,这样才不会抛异常。

除了extern "C"关键字外,每个JNI函数前面还有一个JNIEXPORT关键字?JNIEXPORT有什么作用?默认情况下,这个关键字并没有什么作用,但是如果在Android.mk中指定了

LOCAL_CFLAGS := -fvisibility=hidden

那么,只有使用JNIEXPORT关键字声明的函数才会出现在符号表中。
换名话说,当我们在Android.mk没有指定-fvisibility=hidden时,所有的函数都会出现在SO的符号表中。
当我们在Android.mk中指定了-fvisibility=hidden时,只有使用JNIEXPORT关键字声明的函数才会出现在符号表中。只有函数导出到符号表,java的native函数才有可能链接到C/C++函数,否则会抛出java.lang.UnsatisfiedLinkError异常。
一般情况下我们都应当在Android.mk中使用-fvisibility=hidden标志,这样可以有效的减小SO的大小。
总结一下,当遇到java.lang.UnsatisfiedLinkError时我们应当从以下几个方面来排查自己的代码:

  • 在调用native函数之前是否调用了System.loadLirary函数加载SO?
  • 函数是否使用extern "C"声明?
  • 函数是否使用JNIEXPORT关键字声明?

下一篇 深入浅出Android NDK之使用RegisterNatives函数动态注册native函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值