相对于NDK来讲SDK里面有更多API能够调用,有时候咱们在作NDK开发的时候,须要在JNI
直接Java中的方法和变量,好比callback
,系统信息等....
如何在JNI
中调用Java方法呢?就须要先了解FindClass
和GetMethodID
了。java
在JNI中能够经过FindClass
能够找到Java类,获得jclass
,例如:android
jclass clz=(*env)->FindClass(env,"com/jjz/JniHandle");git
FindClass
的第二个参数须要传入类的完整包名。github
使用GetMethodID
能够获取类的方法,获得jmethodID,例如:数组
jmethodID getStringFromJava=(*env)->GetMethodID(env,class,"getStringForJava","()V");app
若是调用的是静态方法须要使用GetStaticMethodID
获取。经过FindeClass
能够在JNI中找到须要调用的类,GetMethodID
能够找到对应的方法,这样就能够在JNI中调用Java的方法了。
在GetMethodID中,第四个参数是()V
,这个是方法签名。那么方法签名的规则又是怎么样呢?函数
在GetMethodID
中第四个参数()V
就是方法签名,Java是支持重载的,因此须要标明方法的传参和返回值,这就是方法的签名。它是用来保证方法的惟一性。其中()
表明不传参数,V
表明返回值为void。
方法签名对于Java的类型都有一一对应的值。方法签名中用大写的字母对应了java的基本数据类型:工具
Z -> booleanui
B -> bytedebug
C -> char
S -> short
I -> int
J -> long
F -> float
D -> double
其实就是有两个比较特殊的:boolean
对应的是Z,long
对应的J,其余的都是首个字母的大写便可。
数组的表示方法,以[
为标志,一个[
标识一维数组,[[
表示二维数组,例如:
byte[] -> [B
int[][] -> [[I
引用类型的表示方法,须要以L
开头,以;
结束,中间对应类型的包名加类名,例如:
String -> Ljava/lang/String;
Object -> Ljava/lang/Object;
自定义类的表示方法,好比包名为jjz.example,类名为JniHandle的表示方法:
jjz.example.JniHandle ->Ljjz/example/JniHandle;
除了手动输入类名和方法签名之外,JDK还提供了直接生成方法签名的工具javap
。
在build
以后能够在路径../app/build/intermediates/classes/debug
下能够找到build以后生成的.class
文件,运行命令:
javap -s com/jjz/JniHandle
就能够获得这个类的全部的方法签名:
Compiled from "JniHandle.java" public class com.jjz.JniHandle { public com.jjz.JniHandle(); descriptor: ()V public static java.lang.String getStringFromStatic(); descriptor: ()Ljava/lang/String; public java.lang.String getStringForJava(); descriptor: ()Ljava/lang/String; }
有了类的引用,和方法的签名就能够直接在JNI
中调用Java方法了,下面分别介绍下静态方法和类方法的调用。
调用类的静态方法,首先要获得类的引用,再调用类的静态方法。
先定义一个类和静态方法用来提供给JNI
调用:
public class JniHandle { public static String getStringFromStatic() { return "string from static method in java"; } }
在定义了一个native
方法com.jjz.NativeUtil.callJavaStaticMethodFromJni
,生成这个方法的JNI
代码,在JNI
代码中调用JniHandle类的静态方法:
JNIEXPORT void JNICALL Java_com_jjz_NativeUtil_callJavaStaticMethodFromJni(JNIEnv *env, jclass type) { jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle"); if (NULL == jniHandle) { LOGW("can't find JniHandle"); return; } jmethodID getStringFromStatic = (*env)->GetStaticMethodID(env, jniHandle, "getStringFromStatic", "()Ljava/lang/String;"); if (NULL == getStringFromStatic) { (*env)->DeleteLocalRef(env, jniHandle); LOGW("can't find method getStringFromStatic from JniHandle "); return; } jstring result = (*env)->CallStaticObjectMethod(env, jniHandle, getStringFromStatic); const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL); (*env)->DeleteLocalRef(env, jniHandle); (*env)->DeleteLocalRef(env, result); LOGW(resultChar); }
在Java中调用com.jjz.NativeUtil.callJavaStaticMethodFromJni
能够该方法能够在logcat中看到string from static method in java
,这样就完成了在JNI
中调用了Java
静态方法。
调用类方法要更加的复杂一些,调用步骤:
经过findClass找到类
经过GetMethodID获得构造函数
经过调用构造函数获得一个类的实例
经过GetMethodID获得须要调用的方法
使用类的实例调用方法
先定义一个类方法:
public class JniHandle { public String getStringForJava() { return "string from method in java"; } }
再定义一个native
方法:com.jjz.NativeUtil.callJavaMethodFromJni
,生成该方法的JNI
代码,在JMI
代码中实现调用JniHandle
的类方法getStringForJava,代码以下:
JNIEXPORT void JNICALL Java_com_jjz_NativeUtil_callJavaMethodFromJni(JNIEnv *env, jclass type) { jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle"); if (NULL == jniHandle) { LOGW("can't find jniHandle"); return; } jmethodID constructor = (*env)->GetMethodID(env, jniHandle, "<init>", "()V"); if (NULL == constructor) { LOGW("can't constructor JniHandle"); return; } jobject jniHandleObject = (*env)->NewObject(env, jniHandle, constructor); if (NULL == jniHandleObject) { LOGW("can't new JniHandle"); return; } jmethodID getStringForJava = (*env)->GetMethodID(env, jniHandle, "getStringForJava", "()Ljava/lang/String;"); if (NULL == getStringForJava) { LOGW("can't find method of getStringForJava"); (*env)->DeleteLocalRef(env, jniHandle); (*env)->DeleteLocalRef(env, jniHandleObject); return; } jstring result = (*env)->CallObjectMethod(env, jniHandleObject, getStringForJava); const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL); (*env)->DeleteLocalRef(env, jniHandle); (*env)->DeleteLocalRef(env, jniHandleObject); (*env)->DeleteLocalRef(env, result); LOGW(resultChar); }
调用方法com.jjz.NativeUtil.callJavaMethodFromJni
便可看到Java中的字符串传递给了JNI
最后输出到了Logcat上。
在上面的代码中有一个方法叫作DeleteLocalRef
,它的意思是释放局部引用,Android VM
释放局部引用有两种方法:
本地方法执行完毕以后VM
自动释放
经过调用DeleteLocalRef
手动释放
既然上面说了VM
会自动释放引用为何还须要手动释放呢?
其实某些局部变量会阻止它所引用的对象被GC回收,它所引用的对象没法被GC回收,本身自己也就没法被自动释放,所以须要使用DeleteLocalRef
。而这里使用了JNI Local Reference,在JNI中引用了Java对象,若是不使用DeleteLocalRef
释放的话,引用没法回收,就会形成内存泄露。