JNI语法小结

JNI是什么?

JNI的全称就是Java Native Interface,顾名思义,就是Java和C/C++相互通信的接口,就好比买卖房子都需要找中介一样,这里的JNI就是Java和C/C++通信的中介,一个中间人。

JNI头文件

JNI开发前提是要引入jni.h头文件,这个文件Android NDK目录下面。示例如下:

include<jni.h>

怎么加载so库?Android提供了3个实用的函数用来加载JNI库,分别是

System.loadLibrary(libname)
Runtime.getRuntime().loadLibrary(libname)
Runtime.getRuntime().load(libFilePath)

用loadLibrary函数加载

用System.loadLibrary(libname)和Runtime.getRuntime().loadLibrary(libname)这两个函数加载so库,不需要指定so库的路径,Android会默认从系统的共享库目录里面去查找,Android的共享库目录就是vendor/lib和system/lib,如果在共享库路径里面找到指定名字的so库,就会立即加载这个so库,所以我们给so库起名的时候要尽量避免和Android共享库里面的so库同名。如果在共享库目录里面查找不到,就会在APP的安装目录里面查找APP的私有so库,如果查找到,会立即加载这个so库。

用load函数加载

Runtime.getRuntime().load(libFilePath)用这个函数加载so库,需要指定完整的so库路径,优点是加载速度快,并且不会加载错误的so库,缺点就是需要指定完整的so库路径,有时候并不方便,大家常用的加载so库的方式还是用loadLibrary函数来加载。

加载so库示例

static {
    System.loadLibrary("native-lib");
    //用这种方式加载so库和System.loadLibrary函数加载so库的效果是一样的
    //Runtime.getRuntime().loadLibrary("native-lib");
    //String soLibFilePath;
    //用这种方式加载so库需要指定完整的so库路径
    //Runtime.getRuntime().load(soLibFilePath);
}
Android Studio so库配置

Android Studio通过CMakeLists.txt文件配置需要生成的so库,下面详细介绍一下这个CMakeLists.txt文件如何配置。

Android Studio通过cmake命令来生成so库。

CMakeLists.txt文件配置详解

add_library

add_library函数用来配置要生成的so库的基本信息,比如库的名字,要生成的so库是静态的还是共享的,so库的C/C++源文件列表

示例如下:

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp
             src/main/cpp/native-lib2.cpp
             src/main/cpp/native-lib3.cpp)

第一个参数是so库的名字

第二个参数是要生成的so库的类型,静态so库是STATIC,共享so库是SHARED

第三个参数是C/C++源文件,可以包括多个源文件

find_library

find_library函数用来从NDK目录下面查找特定的so库。示例如下:

find_library( log-lib
              log )

第一个参数是我们给要查找的so库起的名字,名字可以随便写

第二个参数是要查找的so库的名字,这个名字是从真实的so库的名字去掉前缀和后缀后的名字,比如liblog.so这个so库的名字就是log

target_link_libraries

target_link_libraries函数用来把要生成的so库和依赖的其它so库进行链接,生成我们需要的so库文件。示例如下:

target_link_libraries( native-lib
                       ${log-lib} )

第一个参数是我们要生成的so库的名字去掉前缀和后缀后的名字,在这个例子中,要生成的真实的so库的名字是libnative-lib.so

第二个参数是链接我们用find_library函数定义的查找的依赖库的名字log-lib,语法就是${依赖的库的名字}

Java和JNI类型对照表

这里详细介绍一下Java类型和C/C++类型的对照关系,方便我们下面的学习,这一部分知识很基础,也很重要。

Java和JNI基本类型对照表

Java的基本类型可以直接与C/C++的基本类型映射,因此Java的基本类型对开发人员是透明的。

Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
Object jobject 任何Java对象,或者没有对应java类型的对象
JNI字符串相关的函数

C/C++字符串转JNI字符串

NewString函数用来生成Unicode JNI字符串

NewStringUTF函数用来生成UTF-8 JNI字符串

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv* env, jobject thiz,jstring jstr) {
    char *str="helloboy";
    jstring jstr2=env->NewStringUTF(str);

    const jchar *jchar2=env->GetStringChars(jstr,NULL);
    size_t len=env->GetStringLength(jstr);
    jstring jstr3=env->NewString(jchar2,len);
}

JNI字符串转C/C++字符串

GetStringChars函数用来从jstring获取Unicode C/C++字符串

GetStringUTFChars函数用来从jstring获取UTF-8 C/C++字符串

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv* env, jobject thiz,jstring jstr) {
    const char *str=env->GetStringUTFChars(jstr,NULL);
    const jchar *jchar2=env->GetStringChars(jstr,NULL);
}

释放JNI字符串

ReleaseStringChars函数用来释放Unicode C/C++字符串

ReleaseStringUTFChars函数用来释放UTF-8 C/C++字符串

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv* env, jobject thiz,jstring jstr) {
    const char *str=env->GetStringUTFChars(jstr,NULL);
   env->ReleaseStringUTFChars(jstr,str);
    
    const jchar *jchar2=env->GetStringChars(jstr,NULL);
   env->ReleaseStringChars(jstr,jchar2);
}

JNI字符串截取

GetStringRegion函数用来截取Unicode JNI字符串

GetStringUTFRegion函数用来截取UTF-8 JNI字符串

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv* env, jobject thiz,jstring jstr) {
    const char *str=env->GetStringUTFChars(jstr,NULL);
    char *subStr=new char;
    env->GetStringUTFRegion(jstr,0,3,subStr);
   env->ReleaseStringUTFChars(jstr,str);

    const jchar *jchar2=env->GetStringChars(jstr,NULL);
    jchar *subJstr=new jchar;
    env->GetStringRegion(jstr,0,3,subJstr);
   env->ReleaseStringChars(jstr,jchar2);
}

获取JNI字符串的长度

GetStringLength用来获取Unicode JNI字符串的长度

GetStringUTFLength函数用来获取UTF-8 JNI字符串的长度

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv* env, jobject thiz,jstring jstr) {
    jsize len=env->GetStringLength(jstr);
    jsize len2=env->GetStringUTFLength(jstr);
}

获取JNI基本类型数组元素

Get<Type>ArrayElements函数用来获取基本类型JNI数组的元素,这里面的<Type>需要被替换成实际的类型,比如GetIntArrayElements,GetLongArrayElements等

示例代码如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv* env, jobject thiz,jintArray array) {
    jint *intArray=env->GetIntArrayElements(array,NULL);
    int len=env->GetArrayLength(array);
    for(int i=0;i<len;i++){
        jint item=intArray[i];
    }
}

获取JNI基本类型数组的子数组

Get<Type>ArrayRegion函数用来获取JNI数组的子数组,这里面的<Type>需要被替换成实际的类型,比如GetIntArrayRegion,GetLongArrayRegion等

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv* env, jobject thiz,jintArray array) {
    jint *subArray=new jint;
    env->GetIntArrayRegion(array,0,3,subArray);
}

设置JNI基本类型数组的子数组

Set<Type>ArrayRegion函数用来获取JNI基本类型数组的子数组,这里面的<Type>需要被替换成实际的类型,比如SetIntArrayRegion,SetLongArrayRegion等

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv* env, jobject thiz,jintArray array) {
    jint *subArray=new jint;
    env->GetIntArrayRegion(array,0,3,subArray);
    env->SetIntArrayRegion(array,0,3,subArray);
}

JNI对象数组

GetObjectArrayElement函数用来获取JNI对象数组元素

SetObjectArrayElement函数用来设置JNI对象数组元素

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJObjectArray(JNIEnv* env, jobject thiz,jobjectArray array) {
    int len=env->GetArrayLength(array);
    for(int i=0;i<len;i++)
    {
        jobject item=env->GetObjectArrayElement(array,i);
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJStringArray(JNIEnv* env, jobject thiz,jobjectArray array) {
    int len=env->GetArrayLength(array);
    for(int i=0;i<len;i++)
    {
        jstring item=(jstring)env->GetObjectArrayElement(array,i);
    }
}

 

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJObjectArray(JNIEnv* env, jobject thiz,jobjectArray array) {
    jobject obj;
    env->SetObjectArrayElement(array,1,obj);
}

获取JNI数组的长度

GetArrayLength用来获取数组的长度

示例如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJObjectArray(JNIEnv* env, jobject thiz,jobjectArray array) {
    int len=env->GetArrayLength(array);
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv* env, jobject thiz,jintArray array) {
    int len=env->GetArrayLength(array);
}

JNI NIO缓冲区相关的函数

使用NIO缓冲区可以在Java和JNI代码中共享大数据,性能比传递数组要快很多,当Java和JNI需要传递大数据时,推荐使用NIO缓冲区的方式来传递。

NewDirectByteBuffer函数用来创建NIO缓冲区

GetDirectBufferAddress函数用来获取NIO缓冲区的内容

GetDirectBufferCapacity函数用来获取NIO缓冲区的大小

示例代码如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testDirectBuffer(JNIEnv* env, jobject thiz) {
    const char *data="hello world";
    int len=strlen(data);
    jobject obj=env->NewDirectByteBuffer((void*)data,len);
    long capicity=env->GetDirectBufferCapacity(obj);
    char *data2=(char*)env->GetDirectBufferAddress(obj);
}

JNI访问Java类的方法和字段

Java类型签名映射表

JNI获取Java类的方法ID和字段ID,都需要一个很重要的参数,就是Java类的方法和字段的签名,这个签名需要通过下面的表来获取。

Java类型 签名
Boolean Z
Byte B
Char C
Short S
Integer I
Long J
Float F
Double D
Void V
任何Java类的全名 L任何Java类的全名;比如Java String类对应的签名是Ljava/lang/String;
type[] type[这个就是Java数组的签名,比如Java int[]的签名是[I,Java long[]的签名就是[J,Java String[]的签名是 [Ljava/lang/String;
方法类型 (参数类型)返回值 类型,比如Java方法void hello(String msg,String msg2)对应的签名就是(Ljava/lang/String;Ljava/lang/String;)V 再比如Java方法String getNewName(String name)对应的签名是(Ljava/lang/String;) Ljava/lang/String;

再比如Java方法long add(int a,int b)对应的签名是(II)J

JNI访问Java类方法相关的函数

JNI访问Java类的实例方法

GetObjectClass函数用来获取Java对象对应的类类型

GetMethodID函数用来获取Java类实例方法的方法ID

Call<Type>Method函数用来调用Java类实例特定返回值的方法,比如CallVoidMethod,调用java没有返回值的方法,CallLongMethod用来调用Java返回值为Long的方法,等等。

示例如下:

Java代码:

public native void callJavaHelloWorld2();
 
public void helloWorld2(String msg){
    Log.i("hello","hello world "+msg);
}
 
JNI代码:
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld2(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorld2_methodID=env->GetMethodID(clazz,"helloWorld2","(Ljava/lang/String;)V");
    if(helloWorld2_methodID==NULL) return;
    const char *msg="hello world";
    jstring jmsg=env->NewStringUTF(msg);
    env->CallVoidMethod(thiz,helloWorld2_methodID,jmsg);
}

JNI访问Java类的静态方法

GetObjectClass函数用来获取Java对象对应的类类型

GetStaticMethodID函数用来获取Java类静态方法的方法ID

CallStatic<Type>Method函数用来调用Java类特定返回值的静态方法,比如CallStaticVoidMethod,调用java没有返回值的静态方法,CallStaticLongMethod用来调用Java返回值为Long的静态方法,等等。

示例如下:

Java代码:

public native void callStaticJavaHelloWorld2();
 
public static void helloWorldStatic2(String msg){
    Log.i("hello","hello world static "+msg);
}
JNI代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld2(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorldStatic2_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic2","(java/lang/String;)V");
    if(helloWorldStatic2_methodID==NULL) return;
    const char *msg="hello world";
    jstring jmsg=env->NewStringUTF(msg);
    env->CallStaticVoidMethod(clazz,helloWorldStatic2_methodID,msg);
}

JNI访问Java类字段相关的函数

JNI访问Java类实例字段

GetFieldID函数用来获取Java字段的字段ID

Get<Type>Field用来获取Java类字段的值,比如用GetIntField函数获取Java int型字段的值,用GetLongField函数获取Java long字段的值,用GetObjectField函数获取Java引用类型字段的值

示例如下:

Java代码:

public class Person{
    public String name;
    public int age;
}

public native void getJavaObjectField(Person person);
 
private void test(){
    Person person=new Person();
    person.name="wubb";
    person.age=20;
    getJavaObjectField(person);
}
 

JNI代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_getJavaObjectField(JNIEnv* env, jobject thiz,jobject person) {
    jclass clazz=env->GetObjectClass(person);
    jfieldID name_fieldID=env->GetFieldID(clazz,"name","Ljava/lang/String;");
    jstring name=(jstring) env->GetObjectField(person,name_fieldID);

    jfieldID age_fieldID=env->GetFieldID(clazz,"age","I");
    jint age=env->GetIntField(person,age_fieldID);
}

JNI访问Java类静态字段

GetStaticFieldID函数用来获取Java静态字段的字段ID

GetStatic<Type>Field用来获取Java类静态字段的值,比如用GetStaticIntField函数获取Java 静态int型字段的值,用GetStaticLongField函数获取Java 静态long字段的值,用GetStaticObjectField函数获取Java静态引用类型字段的值

示例如下:

Java代码:

public class Person {
    public String name;
    public int age;

    public static String name_static;
    public static int age_static;
}
 
public native void getJavaObjectStaticField(Person person);
 
private void test(){
    Person.name_static="wubb";
    Person.age_static=20;

    Person person=new Person();
    getJavaObjectStaticField(person);
}
 
JNI代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_getJavaObjectStaticField(JNIEnv* env, jobject thiz,jobject person) {
    jclass clazz=env->GetObjectClass(person);
    jfieldID name_fieldID=env->GetStaticFieldID(clazz,"name_static","Ljava/lang/String;");
    jstring name=(jstring) env->GetStaticObjectField(clazz,name_fieldID);

    jfieldID age_fieldID=env->GetStaticFieldID(clazz,"age_static","I");
    jint age=env->GetStaticIntField(clazz,age_fieldID);
}

JNI线程同步相关的函数

JNI可以使用Java对象进行线程同步

MonitorEnter函数用来锁定Java对象

MonitorExit函数用来释放Java对象锁

示例如下:

Java代码:

jniLock(new Object());
 
JNI代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_jniLock(JNIEnv* env, jobject thiz,jobject obj) {
    env->MonitorEnter(obj);
    //do something
    env->MonitorExit(obj);
}

JNI异常相关的函数

JNI处理Java异常

当JNI函数调用的Java方法出现异常的时候,并不会影响JNI方法的执行,但是我们并不推荐JNI函数忽略Java方法出现的异常继续执行,这样可能会带来更多的问题。我们推荐的方法是,当JNI函数调用的Java方法出现异常的时候,JNI函数应该合理的停止执行代码。

ExceptionOccurred函数用来判断JNI函数调用的Java方法是否出现异常

ExceptionClear函数用来清除JNI函数调用的Java方法出现的异常

请看如下示例:

Java代码

public void helloWorld(){
    throw new NullPointerException("null pointer occurred");
    //Log.i("hello","hello world");
}
C++代码

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorld_methodID=env->GetMethodID(clazz,"helloWorld","()V");
    if(helloWorld_methodID==NULL) return;
    env->CallVoidMethod(thiz,helloWorld_methodID);
    if(env->ExceptionOccurred()!=NULL){
        env->ExceptionClear();
        __android_log_print(ANDROID_LOG_VERBOSE,"hello","%s","program end with java exception");
        return;
    }
    __android_log_print(ANDROID_LOG_VERBOSE,"hello","%s","program end normallly");
}

JNI抛出Java类型的异常

JNI通过ThrowNew函数抛出Java类型的异常

示例如下:

Java代码

try
{
    testNativeException();
}
catch (NullPointerException e){
    e.printStackTrace();
}
C++代码

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testNativeException(JNIEnv* env, jobject thiz) {
    jclass clazz=env->FindClass("java/lang/NullPointerException");
    if(clazz==NULL) return;
    env->ThrowNew(clazz,"null pointer exception occurred");
}

JNI对象的全局引用和局部引用

我们知道Java代码的内存是由垃圾回收器来管理,而JNI代码则不受Java的垃圾回收器来管理,所以JNI代码提供了一组函数,来管理通过JNI代码生成的JNI对象,比如jobject,jclass,jstring,jarray等,对于这些对象,我们不能简单的在JNI代码里面声明一个全局变量,然后把JNI对象赋值给全局变量,我们需要采用JNI代码提供的专有函数来管理这些全局的JNI对象。

JNI对象的局部引用

在JNI接口函数中引用JNI对象的局部变量,都是对JNI对象的局部引用,一旦JNI接口函数返回,所有这些JNI对象都会被自动释放。不过我们也可以采用JNI代码提供的DeleteLocalRef函数来删除一个局部JNI对象引用。

示例代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testDeleteLocalRef(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorld_methodID=env->GetMethodID(clazz,"helloWorld","()V");
    if(helloWorld_methodID==NULL) return;
   env->CallVoidMethod(thiz,helloWorld_methodID);
    env->DeleteLocalRef(clazz);
}

JNI对象的全局引用

对于JNI对象,绝对不能简单的声明一个全局变量,在JNI接口函数里面给这个全局变量赋值这么简单,一定要使用JNI代码提供的管理JNI对象的函数,否则代码可能会出现预想不到的问题。JNI对象的全局引用分为两种,一种是强全局引用,这种引用会阻止Java的垃圾回收器回收JNI代码引用的Java对象,另一种是弱全局引用,这种全局引用则不会阻止垃圾回收器回收JNI代码引用的Java对象。

强全局引用

NewGlobalRef用来创建强全局引用的JNI对象

DeleteGlobalRef用来删除强全局引用的JNI对象

示例如下:

jobject gThiz;
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testStrongGlobalRef(JNIEnv* env, jobject thiz) {
    //gThiz=thiz;//不能这样给全局JNI对象赋值,要采用下面这种方式
    gThiz=env->NewGlobalRef(thiz);//生成全局的JNI对象引用,这样生成的全局的JNI对象才可以在其它函数中使用

    env->DeleteGlobalRef(gThiz);//假如我们不需要gThiz这个全局的JNI对象引用,我们可以把它删除掉
}

弱全局引用

NewWeakGlobalRef用来创建弱全局引用的JNI对象

DeleteWeakGlobalRef用来删除弱全局引用的JNI对象

IsSameObject用来判断两个JNI对象是否相同

示例如下:

jobject gThiz;
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testWeakGlobalRef(JNIEnv*env, jobject thiz) {
    //gThiz=thiz;//不能这样给全局JNI对象赋值,要采用下面这种方式
    gThiz=env->NewWeakGlobalRef(thiz);//生成全局的JNI对象引用,这样生成的全局的JNI对象才可以在其它函数中使用

    if(env->IsSameObject(gThiz,NULL)){
        //弱全局引用已经被Java的垃圾回收器回收
    }

    env->DeleteWeakGlobalRef(gThiz);//假如我们不需要gThiz这个全局的JNI对象引用,我们可以把它删除掉
}

Java代码和JNI代码通信

Java通过JNI接口调用C/C++方法
首先我们需要在Java代码里面声明Native方法原型,比如:

public native void helloJNI(String msg);
 
其次我们需要在C/C++代码里面声明JNI方法原型,比如:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv* env, jobject thiz,jstring msg) {
    //do something
}
现在这段JNI函数声明代码采用的是C++语言写的,所以需要添加extern "C"声明,如果源代码是C语言,则不需要添加这个声明。

JNIEXPORT 这个关键字说明这个函数是一个可导出函数,学过C/C++的朋友都知道,C/C++ 库里面的函数有些可以直接被外部调用,有些不可以,原因就是每一个C/C++库都有一个导出函数列表,只有在这个列表里面的函数才可以被外部直接调用,类似Java的public函数和private函数的区别。

JNICALL 说明这个函数是一个JNI函数,用来和普通的C/C++函数进行区别,实际发现不加这个关键字,Java也是可以调用这个JNI函数的。

Void 说明这个函数的返回值是void,如果需要返回值,则把这个关键字替换成要返回的类型即可。

Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv*env, jobject thiz,jstring msg)这是完整的JNI函数声明,JNI函数名的原型如下:

Java_ + JNI方法所在的完整的类名,把类名里面的”.”替换成”_” + 真实的JNI方法名,这个方法名要和Java代码里面声明的JNI方法名一样+ JNI函数必须的默认参数(JNIEnv* env, jobjectthiz)

env参数是一个指向JNIEnv函数表的指针,

thiz参数代表的就是声明这个JNI方法的Java类的引用

msg参数就是和Java声明的JNI函数的msg参数对于的JNI函数参数

JNI函数的原型

[extern “C”]JNIEXPORT 函数返回值 JNICALL 完整的函数声明(JNIENV *env, jobject thiz, …)

其中extern “C”根据需要动态添加,如果是C++代码,则必须要添加extern “C”声明,如果是C代码,则不用添加

静态JNI方法和实例JNI方法区别

先看一个示例:

Java代码:

public native void showHello();
public native static void showHello2();
C++代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_showHello(JNIEnv* env, jobject thiz) {
    //do something
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_showHello2(JNIEnv* env, jclass thiz) {
    //do something
}

相信明眼的同学很快就能发现这两个JNI函数的区别,对就是这个区别,普通的JNI方法对应的JNI函数的第二个参数是jobject类型,而静态的JNI方法对应的JNI函数的第二个参数是jclass类型

常见的Java JNI方法声明和JNI函数声明示例

Java Native方法声明:

public class Person{
    public String name;
    public int age;
}

public native void helloJNI(String msg);
public native int func1(int a,int b);
public native String func2(String str);
public native void func3(boolean b);
public native void func4(Person person);
public native static void func5();
 
C++JNI函数声明:

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv* env, jobject thiz,jstring msg) {
    //do something
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_func1(JNIEnv* env, jobject thiz,jint a,jint b) {
    //do something
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_func2(JNIEnv* env, jobject thiz,jstring str) {
    //do something
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_func3(JNIEnv* env, jobject thiz,jboolean b) {
    //do something
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_func4(JNIEnv* env, jobject thiz,jobject person) {
    //do something
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_func5(JNIEnv* env, jclass thiz) {
    //do something
}
 
所有的Java类对象在JNI函数里面都使用jobject来表示

JNI代码和Java代码通信

C++调用Java实例方法示例

Java代码

public native void callJavaHelloWorld();
public native void callJavaHelloWorld2();
public native void callJavaHelloWorld3();
 

public void helloWorld(){
    Log.i("hello","helloworld");
}

public void helloWorld2(String msg){
    Log.i("hello","helloworld "+msg);
}

public void helloWorld3(inta,int b){
    int c=a+b;
    Log.i("hello","helloworld c="+c);
}

C++代码

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorld_methodID=env->GetMethodID(clazz,"helloWorld","()V");
    if(helloWorld_methodID==NULL) return;
    env->CallVoidMethod(thiz,helloWorld_methodID);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld2(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorld2_methodID=env->GetMethodID(clazz,"helloWorld2","(java/lang/String;)V");
    if(helloWorld2_methodID==NULL) return;
    const char *msg="hello world";
    jstring jmsg=env->NewStringUTF(msg);
    env->CallVoidMethod(thiz,helloWorld2_methodID,jmsg);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld3(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorld3_methodID=env->GetMethodID(clazz,"helloWorld3","(II)V");
    if(helloWorld3_methodID==NULL) return;
    env->CallVoidMethod(clazz,helloWorld3_methodID,2,3);
}
C++调用Java静态方法示例

Java代码

public native void callStaticJavaHelloWorld();
public native void callStaticJavaHelloWorld2();
public native void callStaticJavaHelloWorld3();

public static void helloWorldStatic(){
    Log.i("hello","helloworld static");
}

public static void helloWorldStatic2(String msg){
    Log.i("hello","helloworld static "+msg);
}

public static void helloWorldStatic3(inta,int b){
    int c=a+b;
    Log.i("hello","helloworld static c="+c);
}

C++代码

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorldStatic_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic","()V");
    if(helloWorldStatic_methodID==NULL) return;
    env->CallStaticVoidMethod(clazz,helloWorldStatic_methodID);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld2(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorldStatic2_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic2","(java/lang/String;)V");
    if(helloWorldStatic2_methodID==NULL) return;
    const char *msg="hello world";
    jstring jmsg=env->NewStringUTF(msg);
    env->CallStaticVoidMethod(clazz,helloWorldStatic2_methodID,msg);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld3(JNIEnv* env, jobject thiz) {
    jclass clazz=env->GetObjectClass(thiz);
    if(clazz==NULL) return;
    jmethodID helloWorldStatic3_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic3","(II)V");
    if(helloWorldStatic3_methodID==NULL) return;
    env->CallStaticVoidMethod(clazz,helloWorldStatic3_methodID,2,3);
}

下载地址:Github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容