在本文我们跳过JNI的底层机制,读者最好先把它想象为本地代码和java代码的粘合剂

通俗地说,JNI是一种技术,通过这种技术可以做到以下两点:
·  Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
·  Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。

Android 中调用C/C++库的步骤:

  • 第一步:通过System.loadLibrary引入C代码库名。
  • 第二步:在cpp目录下的natice-lib.cpp中编写C/C++代码。
  • 第三步:调用C/C++文件中对应的实现方法即可。

JNI/NDK的API

         在C/C++本地代码中访问Java端的代码,一个常见的应用就是获取类的属性和调用类的方法,为了在C/C++中表示属性和方法,JNI在jni.h头文件中定义了jfieldID,jmethodID类型来分别代表Java端的属性和方法。在访问或者设置Java属性的时候,首先就要先在本地代码取得代表该Java属性的jfeldID,然后才能在本地代码中进行Java属性操作,同样,需要调用Java端的方法时,也是需要取得代表该方法的jmethodID才能进行Java方法调用。接下来尝试下如何在native中调用Java中的方法。

JNIEnv 类型和jobject类型

JNIEnv *env和jobjet instance,简单介绍下这两个类型的作用。

JNIEnv 类型

JNIEnv类型实际上代表了Java环境,通过JNIEnv*指针就可以对Java端的代码进行操作。比如我们可以使用JNIEnv来创建Java类中的对象,调用Java对象的方法,获取Java对象中的属性等。

JNIEnv类中有很多函数可以用,如下所示:

  • NewObject: 创建Java类中的对象。
  • NewString: 创建Java类中的String对象。
  • NewArray: 创建类型为Type的数组对象。
  • GetField: 获取类型为Type的字段。
  • SetField: 设置类型为Type的字段的值。
  • GetStaticField: 获取类型为Type的static的字段。
  • SetStaticField: 设置类型为Type的static的字段的值。
  • CallMethod: 调用返回类型为Type的方法。
  • CallStaticMethod: 调用返回值类型为Type的static 方法。
    当然,除了这些常用的函数方法外,还有更多可以使用的函数,可以在jni.h文件中进行查看,或者参考https://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html链接去查询相关方法,上面都说得特别清楚。

好了,说完JNIEnv,接下来我们讲第二个 jobject。

jobject 类型

jobject可以看做是java中的类实例的引用。当然,情况不同,意义也不一样。如果native方法不是static, obj 就代表native方法的类实例。

如果native方法是static, obj就代表native方法的类的class 对象实例(static 方法不需要类实例的,所以就代表这个类的class对象)。

C语言调用java中方法的语法类似于java中的反射,java中的对象映射在C语言中都用jobject表示。

举一个简单的例子:我们在TestJNIBean中创建一个静态方法testStaticCallMethod和非静态方法testCallMethod,我们看在cpp文件中该如何编写?

TestJNIBean的代码:

 public class TestJNIBean{
    public static final String LOGO = "learn android with aserbao";
    static {
        System.loadLibrary("native-lib");
     }
    public native String testCallMethod();  //非静态
 
    public static native String testStaticCallMethod();//静态
 
    public  String describe(){
        return LOGO + "非静态方法";
    }

    public static String staticDescribe(){
        return LOGO + "静态方法";
    }
}
cpp文件中实现:

 extern "C"
 JNIEXPORT jstring JNICALL
 Java_com_example_androidndk_TestJNIBean_testCallMethod(JNIEnv *env, jobject instance) {
    jclass  a_class = env->GetObjectClass(instance);                                   //因为是非静态的,所以要通过GetObjectClass获取对象
    jmethodID  a_method = env->GetMethodID(a_class,"describe","()Ljava/lang/String;");// 通过GetMethod方法获取方法的methodId.
    jobject jobj = env->AllocObject(a_class);                                         // 对jclass进行实例,相当于java中的new
    jstring pring= (jstring)(env)->CallObjectMethod(jobj,a_method);                 // 类调用类中的方法
    char *print=(char*)(env)->GetStringUTFChars(pring,0);                           // 转换格式输出。 
    return env->NewStringUTF(print);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testStaticCallMethod(JNIEnv *env, jclass type) {
    jmethodID  a_method = env->GetMethodID(type,"describe","()Ljava/lang/String;"); // 通过GetMethod方法获取方法的methodId.
    jobject jobj = env->AllocObject(type);                                          // 对jclass进行实例,相当于java中的new
    jstring pring= (jstring)(env)->CallObjectMethod(jobj,a_method);                 // 类调用类中的方法
    char *print=(char*)(env)->GetStringUTFChars(pring,0);                           // 转换格式输出。
    return env->NewStringUTF(print);
}

上面的两个方法最大的区别就是静态方法会直接传入jclass,从而我们可以省去获取jclass这一步,而非静态方法传入的是当前类 

GetFieldID是得到java类中的参数ID,GetMethodID得到java类中方法的ID,它们只能调用类中声明为 public的参数或方法。使用如下:

jfieldID topicFieldId = env->GetFieldID(objectClass,"name", "Ljava/lang/String;");
jmethodID getcName=env->GetMethodID(objectClass,"getcatName","()Ljava/lang/String;");

第一参数是Java 类对象。第二个参数是参数(或方法名),第三个参数是该参数(或方法)的签名。第三个参数由以下方法得到。

在C/C++中调用Java代码

如何获取Java中的类并生成对象

JNIEnv类中有如下几个方法可以获取java中的类:

  • jclass FindClass(const char* name) 根据类名来查找一个类,完整类名
需要我们注意的是,FindClass方法参数name是某个类的完整路径。比如我们要调用Java中的Date类的getTime方法,那么我们就可以这么做:

 

 extern "C"
 JNIEXPORT jlong JNICALL
 Java_com_example_androidndk_TestJNIBean_testNewJavaDate(JNIEnv *env, jobject instance) {
     jclass  class_date = env->FindClass("java/util/Date");//注意这里路径要换成/,不然会报illegal class name
     jmethodID  a_method = env->GetMethodID(class_date,"<init>","()V");
     jobject  a_date_obj = env->NewObject(class_date,a_method);
     jmethodID  date_get_time = env->GetMethodID(class_date,"getTime","()J");
     jlong get_time = env->CallLongMethod(a_date_obj,date_get_time);
     return get_time;
}
  • jclass GetObjectClass(jobject obj) 根据一个对象,获取该对象的类

如何在C/C++中调用Java方法?

在JNIEnv环境下,我们有如下两种方法可以获取方法和属性:

  • GetMethodID: 获取非静态方法的ID;
  • GetStaticMethodID: 获取静态方法的ID;
    来取得相应的jmethodID。

GetMethodID方法如下:

1  jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)

方法的参数说明:

  • clazz: 这个方法依赖的类对象的class对象。
  • name: 这个字段的名称。
  • sign: 这个字段的签名(每个变量,每个方法都有对应的签名)。

在C/C++中修改Java变量

修改Java中对应的变量思路其实也很简单。

  • 找到对应的类对象。
  • 找到类中的需要修改的属性
  • 重新给类中属性赋值
代码如下:

 public class TestJNIBean{
     static {
         System.loadLibrary("native-lib");
     }
      public int modelNumber = 1;
     /**
      * 修改modelNumber属性
      */
     public native void testChangeField();
}

/*
 * 修改属性
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_androidndk_TestJNIBean_testChangeField(JNIEnv *env, jobject instance) {
    jclass  a_class = env->GetObjectClass(instance);                // 获取当前对象的类
    jfieldID  a_field = env->GetFieldID(a_class,"modelNumber","I"); // 提取类中的属性
    env->SetIntField(instance,a_field,100);                         // 重新给属性赋值
}
调用testChangeField()方法后,TestJNIBean中的modelNumber将会修改为100。

在C/C++中操作Java数组

jType* GetArrayElements((Array array, jboolean* isCopy)):这类方法可以把Java的基本类型数组转换成C/C++中的数组。isCopy为true的时候表示数据会拷贝一份,返回的数据的指针是副本的指针。如果false则不会拷贝,直接使用Java数据的指针。不适用isCopy可以传NULL或者0。

newObject 和 AllocObject 区别

(1)AllocObject这是给对象分配了内存空间,并没有变量的初始化、构造方法的调用,

newObject有对变量进行初始化,并调用指定的构造方法

(2)方法签名的获取 使用命令 Javap

例如:javap -classpath F:\JNI\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes -s com.example.jni.CTransferJava

(3)GetMethodID 获取构造函数 使用<init>代替

知识补充链接:https://www.freesion.com/article/3964761069/

https://www.jianshu.com/p/9c05b0173c8d

https://zhuanlan.zhihu.com/p/97691316

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐