Android 动态注册JNI函数

1.JNI函数注册方式

Android开发中,由于种种原因我们需要调用C/C++代码,在这个时候我们就需要使用jni了,

jni在使用时要对定义的函数进行注册,这样Java才能通过native关键字定义的方法找到对应的C/C++函数

注册函数的方法有两种: 静态注册和动态注册

2.静态注册

静态注册方式是比较常用的一种方式,理解和使用比较简单,但操作生成头文件稍微麻烦些

网上教程比较多,这里只简单说一下静态注册步骤:

1.编写一个java类,在里面加载对应的so库并且通过native关键字定义需要调用的函数


[java]  view plain  copy
  1. package com.example.wenzhe.myjni;  
  2.    
  3. /** 
  4.  * Created by wenzhe on 16-1-27. 
  5.  */  
  6. public class JniTest {  
  7.    
  8.     public native int getRandomNum();  
  9.     public native String getNativeString();  
  10.    
  11.     static {  
  12.         System.loadLibrary("HelloJni");  
  13.     }  
  14.    
  15. }  

加载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如下:

[plain]  view plain  copy
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3.    
  4. LOCAL_MODULE := HelloJni  
  5. LOCAL_SRC_FILES := HelloJni.cpp  
  6.    
  7. include $(BUILD_SHARED_LIBRARY)  


Application.mk如下:

[plain]  view plain  copy
  1. #支持标准C++的一些特性  
  2. APP_STL := gnustl_static  
  3. APP_CPPFLAGS := -frtti -fexceptions  
  4. #支持的CPU架构  
  5. APP_ABI := armeabi-v7a  
  6. #Android 版本  
  7. APP_PLATFORM := android-22  
  8.    

其中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文件也和静态注册的一样

注册函数源码


[cpp]  view plain  copy
  1. // jni头文件   
  2. #include <jni.h>  
  3.    
  4. #include <cassert>  
  5. #include <cstdlib>  
  6. #include <iostream>  
  7. using namespace std;  
  8.    
  9.    
  10. //native 方法实现  
  11. jint get_random_num(){  
  12.     return rand();  
  13. }  
  14. /*需要注册的函数列表,放在JNINativeMethod 类型的数组中, 
  15. 以后如果需要增加函数,只需在这里添加就行了 
  16. 参数: 
  17. 1.java代码中用native关键字声明的函数名字符串 
  18. 2.签名(传进来参数类型和返回值类型的说明)  
  19. 3.C/C++中对应函数的函数名(地址) 
  20. */  
  21. static JNINativeMethod getMethods[] = {  
  22.         {"getRandomNum","()I",(void*)get_random_num},  
  23. };  
  24. //此函数通过调用JNI中 RegisterNatives 方法来注册我们的函数  
  25. static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){  
  26.     jclass clazz;  
  27.     //找到声明native方法的类  
  28.     clazz = env->FindClass(className);  
  29.     if(clazz == NULL){  
  30.         return JNI_FALSE;  
  31.     }  
  32.    //注册函数 参数:java类 所要注册的函数数组 注册函数的个数  
  33.     if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){  
  34.         return JNI_FALSE;  
  35.     }  
  36.     return JNI_TRUE;  
  37. }  
  38.    
  39. static int registerNatives(JNIEnv* env){  
  40.     //指定类的路径,通过FindClass 方法来找到对应的类  
  41.     const char* className  = "com/example/wenzhe/myjni/JniTest";  
  42.     return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));  
  43. }  
  44. //回调函数 在这里面注册函数  
  45. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){  
  46.     JNIEnv* env = NULL;  
  47.    //判断虚拟机状态是否有问题  
  48.     if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!= JNI_OK){  
  49.         return -1;  
  50.     }  
  51.     assert(env != NULL);  
  52.     //开始注册函数 registerNatives -》registerNativeMethods -》env->RegisterNatives  
  53.     if(!registerNatives(env)){  
  54.         return -1;  
  55.     }  
  56.     //返回jni 的版本   
  57.     return JNI_VERSION_1_6;  
  58. }  

上面的代码就能实现动态注册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头文件,打开头文件,里面也可以看到对应的签名,它能够自动生成,如果实在

不知道怎么写签名,就生成头文件自己打开看一下就知道了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值