Android JNI基础篇(一)

拥有回忆 提交于 2019-12-06 08:29:11

Android JNI基础篇

前言

JNI学习其实并不难,在这里,我将引导大家学习JNI的基础知识,认真学完本教程,你将更加坚信我说的话。来吧,我们一起学习!

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,语法就是${依赖的库的名字}

 JavaJNI类型对照表

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

JavaJNI基本类型对照表

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

Java类型

JNI类型

C/C++类型

大小

Boolean

jboolean

unsigned char

无符号8位

Byte

jbyte

char

有符号8位

Char

jchar

unsigned short

无符号16位

Short

jshort

short

有符号16位

Integer

jint

int

有符号32位

Long

jlong

long long

有符号64位

Float

jfloat

float

32位浮点值

Double

jdouble

double

64位双精度浮点值

 

JavaJNI引用类型对照表

与Java基本类型不同,引用类型对开发人员是不透明的。Java类的内部数据结构并不直接向原生代码公开。也就是说原生C/C++代码并不能直接访问Java代码的字段和方法。

Java类型

C/C++类型

java.lang.Class

jclass

java.lang.Throwable

jthrowable

java.lang.String

jstring

java.lang.Object

jobject

java.util.Objects

jobjects

java.lang.Object[]

jobjectArray

Boolean[]

jbooleanArray

Byte[]

jbyteArray

Char[]

jcharArray

Short[]

jshortArray

int[]

jintArray

long[]

jlongArray

float[]

jfloatArray

double[]

jdoubleArray

通用数组

jarray

说明任何Java数组在JNI里面都可以使用jarray来表示,比如Java的int[]数组,用JNI可以表示为jintArray,也可以表示为jarray

JNI函数详解

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数组相关的函数

JNI数组相关的类

JNI类

备注

jbooleanArray

对应Java的boolean[]

jbyteArray

对应Java的byte[]

jcharArray

对应Java的char[]

jshortArray

对应Java的short[]

jintArray

对应Java的int[]

jlongArray

对应Java的long[]

jfloatArray

对应Java的float[]

jdoubleArray

对应Java的double[]

jobjectArray

对应Java的对象数组object[]

JNI基本类型数组

获取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","(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);
}
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);
}

本篇结束语

到这里,相信大家对JNI基础知识都会有一个清晰的认识,现在大家就可以运用本篇学到的知识进行JNI开发了,你们都是好样的。

JNI开发真的很简单,大家只要多加练习,肯定能快速掌握,灵活运用本篇学到的JNI基础知识。

加油,看好你们。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!