安卓 jni 开发之 native 方法的动态注册
2016-03-26 18:22
656 查看
博客: 安卓之家
微博: 追风917
CSDN: 蒋朋的家
简书: 追风917
最近一直在搞 jni 开发,里面坑挺多,其实都是自己不注意导致的。如果你不知道 jni,但是你又想了解这个坑,请先到隔壁喝个茶,取点经,隔壁地址:
Android 开发 之 JNI入门 - NDK从入门到精通
昨天突发奇想,既然 native 方法的命名是这样的:
那么如果包名或者类名含有下划线“_”时,会怎么样呢?哈哈哈
很悲剧,按照上面的 native 方法的命名规则,会出现
此时,我又想起了 bunnyblue 大神的名言:
对于程序来言,一切都是可以实现的,就是代价的问题
多么简单粗暴,一切都是可以搞的,那么这个该如何处理呢,就是本篇文章里讲到的动态注册 native 方法
上面就是动态注册的全部代码了,java 代码中的函数是
如果你看不出来,请继续往下看,容我慢慢道来:
动态注册的原理是这样的:JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数,
而不必通过函数名来查找相关函数(这个查找效率很低,函数名超级长)。
Java 与 jni 通过该结构体建立联系,其中有三个变量:
name:Java 中函数的名字。
signature:签名符号,描述了函数的参数和返回值
fnPtr:函数指针,指向一个被调用的函数
我们看上面定义的结构体数组:
可以看出,里面有一个成员,该成员第一个参数 “dynamicRegFromJni”,java 函数名;第二个参数“()Ljava/lang/String:”,是签名符号,意思是该函数没有参数,返回一个字符串;第三个参数就是要调用的 native 方法。
关于
当 java 通过 System.loadLibrary 加载完 JNI 动态库后,紧接着会调用 JNI_OnLoad 的函数。
1 指定 jni 版本:告诉 JVM 该组件使用哪一个 jni 版本(若未提供JNI_OnLoad()函数,JVM 会默认该使用最老的 JNI 1.1版本),如果要使用新版本的JNI,例如JNI 1.4版,则必须由 JNI_OnLoad() 函数返回常量 JNI_VERSION_1_4 (该常量定义在 jni.h 中) 来告知 JVM 。
2 一系列初始化操作,当 JVM 执行到 System.loadLibrary() 函数时,会立即调用 JNI_OnLoad() 方法,因此在该方法中进行各种资源的初始化操作最为恰当,
RegisterNatives在 jni.h 中是这么定义的:
该函数有三个参数:
clazz: java 类名,通过 FindClass 得到
methods:JNINativeMethod 结构体指针
nMethods: 方法个数
我们看上面该函数的调用:
咦,里面怎么又四个参数,嗯,是的,这个是 C 编写规则,如果是 C++ 的话,是这样的:
看出差别了吗,如果你遇到下面类似错误:
可能就是 C 和 C++ 编写规则的问题咯,check 一下,没什么大问题的。
ok,这样就完成了 native 方法的动态注册
这里补充一点,jni 开发中 80% 会发生的错误:
比如我今天遇到了很多次这个错误:
该问题的发生原因很多,我这里报错是因为自己粗心,代码 大小写 写错,类名 写错等,所以这里提醒大家对待你的代码要像你的女朋友一样,千万不可马虎大意呀。
jni 开发告一段落了,这几天总结了很多坑,详情请点击这里:
Android Studio 下 jni 开发填坑记
安卓开源库收集整理中,欢迎 PR, star,地址:https://github.com/XXApple/AndroidLibs
分享是一种美德,更是一种生活方式!!
也许你会说我是一个梦想者,但我不是唯一的一个。
悦分享,越快乐^_^
欢迎交流,转载请注明出处,谢谢!
微博: 追风917
CSDN: 蒋朋的家
简书: 追风917
最近一直在搞 jni 开发,里面坑挺多,其实都是自己不注意导致的。如果你不知道 jni,但是你又想了解这个坑,请先到隔壁喝个茶,取点经,隔壁地址:
Android 开发 之 JNI入门 - NDK从入门到精通
昨天突发奇想,既然 native 方法的命名是这样的:
Java_完整包名类名_方法名()
那么如果包名或者类名含有下划线“_”时,会怎么样呢?哈哈哈
很悲剧,按照上面的 native 方法的命名规则,会出现
Native method not found的异常,那么该如何处理呢?只能保证包名和类名不出现下划线吗?
此时,我又想起了 bunnyblue 大神的名言:
对于程序来言,一切都是可以实现的,就是代价的问题
多么简单粗暴,一切都是可以搞的,那么这个该如何处理呢,就是本篇文章里讲到的动态注册 native 方法
动态注册 native 方法
先上一段代码吧:static jstring nativeDynamicRegFromJni(JNIEnv *env, jobject obj) { return (*env) -> NewStringUTF(env, "动态注册调用成功"); } JNINativeMethod nativeMethod[] = {{"dynamicRegFromJni", "()Ljava/lang/String;", (void*)nativeDynamicRegFromJni}}; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { JNIEnv *env; if ((*jvm) -> GetEnv(jvm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } jclass clz = (*env) -> FindClass(env, "github/jp1017/hellojni/MainActivity"); (*env) -> RegisterNatives(env, clz, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0])); return JNI_VERSION_1_4; }
上面就是动态注册的全部代码了,java 代码中的函数是
dynamicRegFromJni,调用的 native 方法是
nativeDynamicRegFromJni,该方法没有参数,返回值是一个字符串。
如果你看不出来,请继续往下看,容我慢慢道来:
动态注册的原理是这样的:JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数,
而不必通过函数名来查找相关函数(这个查找效率很低,函数名超级长)。
JNINativeMethod
这是一个结构体,在 jni.h 头文件中定义:typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;
Java 与 jni 通过该结构体建立联系,其中有三个变量:
name:Java 中函数的名字。
signature:签名符号,描述了函数的参数和返回值
fnPtr:函数指针,指向一个被调用的函数
我们看上面定义的结构体数组:
JNINativeMethod nativeMethod[] = {{"dynamicRegFromJni", "()Ljava/lang/String;", (void*)nativeDynamicRegFromJni}};
可以看出,里面有一个成员,该成员第一个参数 “dynamicRegFromJni”,java 函数名;第二个参数“()Ljava/lang/String:”,是签名符号,意思是该函数没有参数,返回一个字符串;第三个参数就是要调用的 native 方法。
关于
签名符号可以参考这里:Android JNI编程—JNI基础
当 java 通过 System.loadLibrary 加载完 JNI 动态库后,紧接着会调用 JNI_OnLoad 的函数。
JNI_OnLoad()函数
JNI_OnLoad()函数有两个重要的作用:1 指定 jni 版本:告诉 JVM 该组件使用哪一个 jni 版本(若未提供JNI_OnLoad()函数,JVM 会默认该使用最老的 JNI 1.1版本),如果要使用新版本的JNI,例如JNI 1.4版,则必须由 JNI_OnLoad() 函数返回常量 JNI_VERSION_1_4 (该常量定义在 jni.h 中) 来告知 JVM 。
2 一系列初始化操作,当 JVM 执行到 System.loadLibrary() 函数时,会立即调用 JNI_OnLoad() 方法,因此在该方法中进行各种资源的初始化操作最为恰当,
RegisterNatives
动态注册的工作就是在这里完成的。RegisterNatives在 jni.h 中是这么定义的:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)
该函数有三个参数:
clazz: java 类名,通过 FindClass 得到
methods:JNINativeMethod 结构体指针
nMethods: 方法个数
我们看上面该函数的调用:
(*env) -> RegisterNatives(env, clz, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0]));
咦,里面怎么又四个参数,嗯,是的,这个是 C 编写规则,如果是 C++ 的话,是这样的:
env -> RegisterNatives(clz, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0]));
看出差别了吗,如果你遇到下面类似错误:
error: request for member 'FindClass' in something not a structure or union
可能就是 C 和 C++ 编写规则的问题咯,check 一下,没什么大问题的。
ok,这样就完成了 native 方法的动态注册
这里补充一点,jni 开发中 80% 会发生的错误:
Native method not found
比如我今天遇到了很多次这个错误:
UnsatisfiedLinkError: Native method not found: github.jp1017.hellojni.MainActivity.dynamicRegFromJni:()Ljava/lang/String;
该问题的发生原因很多,我这里报错是因为自己粗心,代码 大小写 写错,类名 写错等,所以这里提醒大家对待你的代码要像你的女朋友一样,千万不可马虎大意呀。
总结
上面的代码我给上传到了 github: https://github.com/jp1017/HelloJnijni 开发告一段落了,这几天总结了很多坑,详情请点击这里:
Android Studio 下 jni 开发填坑记
安卓开源库收集整理中,欢迎 PR, star,地址:https://github.com/XXApple/AndroidLibs
分享是一种美德,更是一种生活方式!!
也许你会说我是一个梦想者,但我不是唯一的一个。
悦分享,越快乐^_^
欢迎交流,转载请注明出处,谢谢!
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题