1.JNI函数注册方式
在Android开发中,由于种种原因我们需要调用C/C++代码,在这个时候我们就需要使用jni了,
jni在使用时要对定义的函数进行注册,这样Java才能通过native关键字定义的方法找到对应的C/C++函数
注册函数的方法有两种: 静态注册和动态注册
2.静态注册
静态注册方式是比较常用的一种方式,理解和使用比较简单,但操作生成头文件稍微麻烦些
网上教程比较多,这里只简单说一下静态注册步骤:
1.编写一个java类,在里面加载对应的so库并且通过native关键字定义需要调用的函数
加载so库不需要写lib前缀
2.在命令行下进入源文件目录,输入 javac JniTest.java 生成JniTest.class文件,.class文件使用来生成头文件的,
然后在src目录下通过 javah com.example.wenzhe.myjni.JniTest 生成 com_example_wenzhe_myjni_JniTest.h 头文件
包名(指定.class 文件路径)一定要写对
3.将头文件拷贝到jni目录下(eclipse在src同级目录建立文件夹,Android studio 在java同级目录建立文件夹)
4.在jni目录下编写C/C++源代码 并把刚拷贝的头文件包含进去 ,复制头文件中函数的定义部分,并实现其中的你想要的功能
编写Android.mk(ndk根据.mk文件定义的规则编译出so库) Application.mk(Application.mk主要用来定义适应的平台,x86 arm等)
Android.mk如下:
其中LOCAL_MODULE定义的名字就是生成的so库名字,so库前面都会有个lib前缀,上面生成的so应该为 libHelloJni.so
5.在命令行中进入jni目录,输入ndk-build 即可生产对应so库,会自动放在libs文件夹下 至此就可以运行程序了
(当然你也可以在IDE中配置好ndk编译的环境,就不用这么麻烦了)
如果使用Android studio 且不做任何设置的话,需将libs目录下的so库拷贝到jniLibs目录下,否则Android studio不会自动将
libs目录下的so库打包到apk中,这是就会出现找不到so库的错误
3.动态注册
由于静态注册每次添加新函数后要重新生成头文件,而且函数名又长,操作起来非常麻烦
我们可以用动态注册来避免这些麻烦 jni中提供了RegisterNatives方法来注册函数
并且我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad 的函数
在这个函数中一般是做一些初始化设定和指定jni版本 我们可以在这个方法里面注册函数
现在我们不需要头文件,只需要 C/C++ 源文件,.mk文件也和静态注册的一样
注册函数源码
上面的代码就能实现动态注册jni了 以后要增加函数只需在java文件中声明native方法,在C/C++文件中实现,
并在getMethods数组添加一个元素并指明对应关系,通过ndk-build 生成so库就可以运行了
上面代码中registerNatives()和 registerNativeMethods()方法是我们自己写的,其实完全可以写到一个函数里面
当初不懂原理的时候看网上教程以为必须要这么写,后来懒得改了,直接拿来用了, 而env->RegisterNatives()方法
是jni中提供的,用来注册我们想要注册的函数
其中jni版本可以在jni.h头文件中去查看支持哪些版本,一般定义在文件最后几行
4.C/C++ - java - jni 对应参数
动态注册中 JNINativeMethod 结构体中第二个参数需注意
括号内代表传入参数的签名符号,如果为空括号内什么都不用写,括号外代表返回参数的签名符号,为空的话要写大写的V,对应关系见下表
签名符号 | JNI | java |
V | void | void |
Z | jboolean | boolean |
I | jint | int |
J | jlong | long |
D | jdouble | double |
F | jfloat | float |
B | jbyte | byte |
C | jchar | char |
S | jshort | short |
[Z | jbooleanArray | boolean[] |
[I | jintArray | int[] |
[J | jlongArray | long[] |
[D | jdoubleArray | double[] |
[F | jfloatArray | float[] |
[B | jbyteArray | byte[] |
[C | jcharArray | char[] |
[S | jshortArray | short[] |
L完整包名加类名; | jobject | class |
举个例子:
传入的java参数有两个 分别是 int 和 long[] 函数返回值为 String 即java函数的定义为:String getString(int a ,long[] b)
签名就应该是 "(I[J)Ljava/lang/String;"
如果有内部类 则用 $ 来分隔 如: Landroid/os/FileUtils$FileStatus; (这个我自己没有试过)
用静态注册方式注册函数时,会生成.h头文件,打开头文件,里面也可以看到对应的签名,它能够自动生成,如果实在
不知道怎么写签名,就生成头文件自己打开看一下就知道了