大家好,欢迎来到IT知识分享网。
目标:认识JNI(Java Native Interface)技术,了解Java调用本地C/C++库的简单方法以及一些基本的知识点
什么是JNI,为什么使用JNI
JNI是Java Native Interface的缩写,中文译为“Java本地接口”。通俗地说,JNI是一种技术,通过这种技术可以做到以下两点:
- Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
- Native程序中的函数可以调用Java层的函数,也就是说在C/C++程序中可以调用Java的函数。
也就是Java与C/C++代码互相调用
在平台无关的Java中,为什么要创建一个与Native相关的JNI技术呢?这岂不是破坏了Java的平台无关特性吗?JNI技术的推出有以下几个方面的考虑:
- 承载Java世界的虚拟机是用Native语言写的,而虚拟机又运行在具体的平台上,所以虚拟机本身无法做到平台无关。然而,有了JNI技术后就可以对Java层屏蔽不同操作系统平台(如Windows和Linux)之间的差异了(例如同样是打开一个文件,Windows上的API使用OpenFile函数,而Linux上的API是open函数)。这样,就能实现Java本身的平台无关特性。其实Java一直在使用JNI技术,只是我们平时较少用到罢了。
- 早在Java语言诞生前,很多程序都是用C/C++语言写的,它们遍布在软件世界的各个角落。Java出世后,它受到了追捧,并迅速得到发展,但仍无法将软件世界彻底改朝换代,于是才有了折中的办法。既然已经有C/C++模块实现了相关功能,那么在Java中通过JNI技术直接使用它们就行了,免得落下重复制造轮子的坏名声。另外,在一些要求效率和速度的场合还是需要C/C++语言参与的。
- 在Android平台上,JNI就是一座将C/C++世界和Java世界间的天堑变成通途的桥。
- 效率上 C/C++是本地语言,比java更高效
- 代码移植,如果之前用C语言开发过模块,可以复用已经存在的c代码
- java反编译比C语言容易,一般加密算法都是用C语言编写,不容易被反编译
第一个JNI应用
方法注册
Java Native Interface 总的来说可以被认为是一个Java接口,但是实现是在Native层代码中,为了能让Java代码正常的调用到Native方法,首先我们得让Java知道Native层对应的函数关系是什么,也就是Java接口对应的Native函数指针是什么,这个对应的关系称为方法注册,一般注册可以分为两种:静态注册 & 动态注册
3.1 静态注册
public native String stringFromJNI();
对应的JNI代码:
extern "C" JNIEXPORT jstring JNICALL Java_com_tis_jni_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
我们新建一个Nateive方法,比如:
public native String stringFromJNI2();
extern "C" JNIEXPORT jstring JNICALL Java_com_tis_jni_MainActivity_stringFromJNI2(JNIEnv *env, jobject thiz) {
// TODO: implement stringFromJNI2() }
3.2 动态注册
typedef struct {
char *name; char *signature; void *fnPtr; } JNINativeMethod;
static const std::string CLASS_NAME = "com/tis/jni/MainActivity"; //(2)在一个JNINativeMethod数组中保存所有native函数和JNI函数的对应关系; static const JNINativeMethod CLASS_METHODS[] = {
//(1)利用结构体JNINativeMethod保存Java Native函数和JNI函数的对应关系; {
"stringFromJNI2", "()Ljava/lang/String;", (void *) (stringFromJNI2)}, }; //(4)JNI_OnLoad中会调用RegisterNatives函数进行函数注册; static int register_JNI(JNIEnv *env, const char *className, const JNINativeMethod *gMethods, int num) {
jclass clazz = env->FindClass(className); if (clazz == nullptr) {
LOGW("find class %s failed\n", className); return JNI_FALSE; } //(5)AndroidRuntime::registerNativeMethods中最终调用RegisterNatives完成注册。 if (env->RegisterNatives(clazz, gMethods, num) < 0) {
return JNI_FALSE; } return JNI_TRUE; } /* * When the Java layer loads the JNI dynamic library through System.loadLibrary, * it will then look for a function called JNI_OnLoad in the library, and call it if there is one. */ //(3)在Java中通过System.loadLibrary加载完JNI动态库之后,调用JNI_OnLoad函数,开始动态注册; jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
JNIEnv *env = NULL; jint result = -1; LOGW("JNI_OnLoad\n"); if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGW("ERROR: GetEnv failed\n"); goto bail; } assert(env != NULL); / * When an array is used as a function parameter, the original array is not copied, * but the pointer to the original array is copied. * So unable to get array size using sizeof(), here you need to provide the array size */ if (register_JNI(env, CLASS_NAME.c_str(), CLASS_METHODS, NELEM(CLASS_METHODS)) < 0) {
LOGW("ERROR: native registration failed\n"); goto bail; } /* success -- return valid version number */ result = JNI_VERSION_1_4; bail: return result; }
4. 类型转换
从上表可看出,规律也很明显,数组的JNI层数据类型需要以“Array”结尾,签名格式的开头都会有“[”。除了数组以外,其他的引用数据类型的签名格式都会以“;”结尾。
5. JNI如何调用Java
{
"stringFromJNI", "()Ljava/lang/String;", (void *) (stringFromJNI)},
javap -s -p MainActivity.class
其中s 表示输出内部类型签名,p表示打印出所有的方法和成员(默认打印public成员),最终在cmd中的打印结果如下:
protected void onCreate(android.os.Bundle); descriptor: (Landroid/os/Bundle;)V public native java.lang.String stringFromJNI(); descriptor: ()Ljava/lang/String; public native java.lang.String stringFormJNI2(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/String;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); } public native String stringFromJNI(); public native String stringFromJNI2(String value);
#if defined(__cplusplus) typedef _JNIEnv JNIEnv;//C++中JNIEnv的类型 typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv;//C中JNIEnv的类型 typedef const struct JNIInvokeInterface* JavaVM; #endif
public static int nativeCallInt(int para1, int[] para2) {
Log.w(TAG, "nativeCallInt " + para1 + " " + Arrays.toString(para2)); return (para1 >= 0 && para1 < para2.length) ? para2[para1] : 0; } public String nativeCallString(String para1, String para2) {
Log.w(TAG, "nativeCallString " + para1 + " " + para2); return para1 + para2; }
他们对应的方法签名:
public static int nativeCallInt(int, int[]); descriptor: (I[I)I public java.lang.String nativeCallString(java.lang.String, java.lang.String); descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
/ * Native re-call java, use like reflect in java */ extern "C" JNIEXPORT void JNICALL callNative(JNIEnv *env, jobject thiz) {
jclass cls; jmethodID mid; jfieldID jFd; // find class object by class name cls = env->FindClass(CLASS_NAME.c_str()); if (cls != nullptr) {
//find static methodId by method name and signature mid = env->GetStaticMethodID(cls, "nativeCallInt", "(I[I)I"); if (mid != nullptr) {
jint para1 = 3; int temp[] = {
1, 2, 3, 4, 5, 6}; jintArray para2 = env->NewIntArray(sizeof(temp) / sizeof(int)); env->SetIntArrayRegion(para2, 0, sizeof(temp) / sizeof(int), temp); // call the method jint result = env->CallStaticIntMethod(cls, mid, para1, para2); LOGW("Native call Java nativeCallInt: %d\n", result); } //find methodId by method name and signature mid = env->GetMethodID(cls, "nativeCallString", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); if (mid != nullptr) {
std::string string1 = "Hello "; std::string string2 = "World!"; jstring para1 = env->NewStringUTF(string1.c_str()); jstring para2 = env->NewStringUTF(string2.c_str()); jstring result = (jstring) env->CallObjectMethod(thiz, mid, para1, para2); std::string ret = env->GetStringUTFChars(result, nullptr); LOGW("Native call Java nativeCallString: %s\n", ret.c_str()); } //find fieldId by field name and signature jFd = env->GetStaticFieldID(cls, "TAG", "Ljava/lang/String;"); if (jFd != nullptr) {
jstring tag = (jstring) env->GetStaticObjectField(cls, jFd); std::string ret = env->GetStringUTFChars(tag, nullptr); LOGW("Native get Java TAG: %s\n", ret.c_str()); } } }
同理也能获取Java层中对象的属性,感兴趣的可以自行尝试一下
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/116623.html


