在Android开发中,因为种种缘由咱们须要调用C/C++代码, 这个时候就要用到Android开发者都据说过的JNI(Java Native Interface)了, 在调用JNI相关方法以前, 要对java中native关键字定义的方法进行注册, 注册方式有两种: 静态注册和动态注册, 二者优缺点以下:java
此注册方法是初学者常常用到的, 比较常见, 这里简单说下流程,
1.编写一个java类,在里面加载对应的so库而且经过native关键字定义须要调用的函数android
package com.example.wenzhe.myjni; /** * Created by wenzhe on 16-1-27. */ public class JniTest { public native int getRandomNum(); public native String getNativeString(); static { System.loadLibrary("HelloJni"); } }
2.在命令行下输入 javac JniTest.java 生成JniTest.class文件
而后在src目录下经过 javah com.example.wenzhe.myjni.JniTest 生成 com_example_wenzhe_myjni_JniTest.h 头文件
3.将头文件拷贝到jni目录下(eclipse在src同级目录创建文件夹,Android studio 在java同级目录创建文件夹)
4.编写C/C++源代码 并把刚拷贝的头文件包含进去 ,复制头文件中函数的定义部分,并实现其中的你想要的功能ios
而后编写Android.mk Application.mk(Application.mk主要用来定义适应的平台,x86 arm等)数组
Android.mk以下:架构
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := HelloJni LOCAL_SRC_FILES := HelloJni.cpp include $(BUILD_SHARED_LIBRARY)
Application.mk以下:dom
#支持标准C++特性 APP_STL := gnustl_static APP_CPPFLAGS := -frtti -fexceptions #支持的CPU架构 APP_ABI := armeabi-v7a #Android 版本 APP_PLATFORM := android-22 include $(BUILD_SHARED_LIBRARY)
其中LOCAL_MODULE定义的名字就是生成的so库名字,so库前面都会有个lib前缀,上面生产的so应该为 libHelloJni.soeclipse
5.在命令行中进入jni目录,输入ndk-build 便可生产对应so库,会自动放在libs文件夹下 至此就能够运行程序了函数
动态注册基本思想是在JNI_Onload()函数中经过JNI中提供的RegisterNatives()方法来将C/C++方法和java方法对应起来(注册), 咱们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad ()的函数,在这个函数中通常是作一些初始化相关操做, 咱们能够在这个方法里面注册函数, 注册总体流程以下:工具
示例代码以下:ui
// jni头文件 #include <jni.h> #include <cassert> #include <cstdlib> #include <iostream> using namespace std; //native 方法实现 jint get_random_num(){ return rand(); } /*须要注册的函数列表,放在JNINativeMethod 类型的数组中, 之后若是须要增长函数,只需在这里添加就好了 参数: 1.java中用native关键字声明的函数名 2.签名(传进来参数类型和返回值类型的说明) 3.C/C++中对应函数的函数名(地址) */ static JNINativeMethod getMethods[] = { {"getRandomNum","()I",(void*)get_random_num}, }; //此函数经过调用RegisterNatives方法来注册咱们的函数 static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){ jclass clazz; //找到声明native方法的类 clazz = env->FindClass(className); if(clazz == NULL){ return JNI_FALSE; } //注册函数 参数:java类 所要注册的函数数组 注册函数的个数 if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){ return JNI_FALSE; } return JNI_TRUE; } static int registerNatives(JNIEnv* env){ //指定类的路径,经过FindClass 方法来找到对应的类 const char* className = "com/example/wenzhe/myjni/JniTest"; return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0])); } //回调函数 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; //获取JNIEnv if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } assert(env != NULL); //注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives if(!registerNatives(env)){ return -1; } //返回jni 的版本 return JNI_VERSION_1_6; }
上面的代码就能实现动态注册JNI了 之后要增长函数只需在java文件中声明native方法,在C/C++文件中实现,
并在getMethods数组添加一个元素并指明对应关系,经过ndk-build 生成so库就能够运行了
其中JNI版本能够在jni.h头文件中去查看支持哪些版本,通常定义在文件最后几行
动态注册中 JNINativeMethod 结构体中第二个参数需注意
括号内表明传入参数的签名符号,为空能够不写,括号外表明返回参数的签名符号,为空填写 V,对应关系入下表
签名符号 | C/C++ | 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 即函数的定义为:String getString(int a ,long[] b)
签名就应该是 :"(I[J)Ljava/lang/String;"(不要漏掉英文分号)
若是有内部类 则用 $ 来分隔 如:Landroid/os/FileUtils$FileStatus;
当熟悉动态注册后, 动态注册无疑是注册函数的更好方式, 惟一要注意的是注册函数时, 须要额外当心, 别把类名,函数名和签名写错了, 否则loadLibraries时会致使应用Crash, 关于JNI部分知识, 我还会写一篇关于如何高效传递数据以及JNI开发过程当中的一些坑的总结.
做者:smewise 连接:https://www.jianshu.com/p/1d6ec5068d05 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。