JNI 是 Java Native Interface(Java 本地接口)。JNI不是Android 专有的东西,他是从Java继承来的。但是 对于Android来说JNI至关重要,Android 作为一种嵌入式操作系统,有大量和驱动、硬件相关的功能都是用C/C++来实现的。可以说在Android中不管应用级还是系统级的开发都离不开JNI。
Java语言的执行,离不开JVM,因此当需要在Java层中调用C/C++层时,需要先告诉JVM那个方法代表本地函数,伊基在哪里能找到这个函数,反之也一样。但是从Java层调用C/C++需要建立函数间的关联;而C/c++到Java。必须先得到Java对象的引用,才能调用该对象的方法(Java纯面向对象)。
需要注意的是,不论是从C/C++到Java 还是从Java到C/C++都是在一个线程中运行的。
从JAVA到C/C++
装载JNI动态库
为了能够使用JNI,在调用本地方法前必须把C/C++代码所在的动态库装载到进程的内存空间中。一般之间使用System的LoadLibrary() 方法
public static void loadLibrary(String libName);
值得一提的是,libName是我们生成的动态库名称的一部分(注意:这里是一部分),在Android 中JNI动态库的名称必须为"lib"开始,那么一般我们的libName就会去掉前缀"lib"和后缀".so"或者“.dll”。加入我们本地的动态库文件为"libjni.so",那么我们的调用就是
System.loadLibrary("jni");
注:动态库的后缀在Linux中是".so",在Window中是“.dll”。
在这里我们并不需要指定我们的动态链接库的具体路劲。Android系统会自动在相关的系统路径里面查找。
一般情况下LoadLibrary()会在类的static代码块中执行(为了保证native方法在调用之间就已经加载完成,static代码块会首先执行);
public class Sample{ static { System.loadLibrary("jni"); } }
定义native方法
Java层申明native方法很简单:
private static native final void native_init();
在native方法中,可以使用任何类型作为参数,包括基本对象类型、数组、复杂对象。Java中调用native方法和普通方法无异。
JNI动态链接库
JNI动态链接库与非动态库的区别就在于:JNI动态库中定义了"JNI_OnLoader"函数,这个函数在System.LoadLibrary();后被系统调用,用于完成JNI函数的注册。
jint JNI_OnLoader(JavaVm* vm, void* reserved)
在"JNI_OnLoader"中,最重要的一件事就是通过registerNativeMethods()函数完成JNI函数的注册(完成Java层中申明的native方法和C/C++中C函数关联);这样一来,我们的JVM在运行native方法时就能够找到对应的C函数。
jnitest.cpp
#define LOG_TAG "hello-JNI" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" static jint com_jeason_jnitest_nadd(JNIEnv *env, jobject obj, jint a, jint b){ return (a * b); } static JNINativeMethod gMethods[] = { {"nadd", "(II)I", (void *)com_jeason_jnitest_nadd}, }; static int register_android_test_add(JNIEnv *env){ return android::AndroidRuntime::registerNativeMethods(env, "com/jeason/jnitest/LoadLibraryJavaClass", gMethods, NELEM(gMethods)); } jint JNI_OnLoad(JavaVM *vm, void *reserved){ JNIEnv *env = NULL; if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) { printf("Error GetEnv\n"); return -1; } assert(env != NULL); if (register_android_test_add(env) < 0) { printf("register_android_test_add error.\n"); return -1; } return JNI_VERSION_1_4; }
registerNativeMethods()函数的原型:
int registerNativeMethods(JNIEnv* env,const char* classname,const JNINativeMethos* gMethods,int numberMethods);
第二个参数className 指的是我们的Java类(调用SYstem.loadLibrary())的路径全名,但是在名称中‘.’需要用路径符‘/‘.
第三个参数gMethods 是JNINativeMethods类型数组
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethods;
其中name 是指Java中申明native方法的名称;signature是指native方法参数签名(后面给出说明);
fnPtr是指native方法对应的本地函数指针,一般都为void* (无法直接指定,一般只能用void*,调用时在强制类型转换)
通常函数原型一般为
JNIEXPORT jint JNICALL com_jeason_jnitest_jnitext_nadd(JNIEnv *env, jobject obj ,jint a,jint b);
JNIEXPORT 和 JNICALL 可以省略
LoadLibraryJavaClass.java
public class LoadLibraryJavaClass { static { System.loadLibrary("jni"); } public native int nadd(int a, int b); }
参数签名
native方法中的参数签名使用了一些缩写符号来代表参数类型,这些符号都是Java语言规定的。签名由参数和返回值组成,参数必须用小括号括起来,没有的时候也必须要使用一对空括号。例如"(I)V"表示方法有一个整型参数,无返回值。"([IZ)I"表示有2个参数,第一个是整形数组第二个是布尔型,返回值为整形。
具体的对应关系见下面两张图:
数组则以”["开始,用两个字符表示
以上都是基本数据类型,前面我们解决了JNI函数的注册问题,下面我们来考虑这样一个问题。在java中调用native函数传递的参数是java数据类型,那么这些参数类型到了JNI会变成什么呢? 下面我们引出了一个新的话题——数据类型转换。
数据类型转换:
在java层调用native函数传递到JNI层的参数,JNI层会做一些特殊处理,我们知道java数据类型分为基本数据类型和引用数据类型两种,JNI层也是区别对待的。下表示出了java数据类型—>native类型的转换。
其中在java数据类型中,除了java中基本数据类型和数组,Class,String,Throwable,其余所有的java对象的数据类型在JNI中用jobject表示。
但是,是不是所有的都这么简单呢
{"myway",“(Ljava.lang.String;Ljava.lang.String;Landroid.bean.Bean;)V”,(void*)myway}
对于JNI的编译:
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := jnitest.cpp LOCAL_SHARED_LIBRARIES := libandroid_runtime LOCAL_MODULE := libjni include $(BUILD_SHARED_LIBRARY)
版权声明:本文为博主原创文章,未经博主允许不得转载。