美文网首页
JNI学习总结(实践篇)

JNI学习总结(实践篇)

作者: peter_RD_nj | 来源:发表于2018-03-07 17:24 被阅读0次

前言

JNI学习总结(基础篇)里面总结了关于JNI和NDK的一些基本概念以及开发流程。本文中学习一下里面涉及到的具体知识。

JNI基础知识

如上篇文中的介绍,如果你用的是2.2之后版本并勾选了"Include C++ support",Android Studio会自动在cpp文件夹下创建了一个名为native-lib.cpp的C++文件,代码如下:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL Java_com_android_peter_jnidemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

示例代码很简单,我们来逐行分析一下。

#include <jni.h>
#include <string>

学过C++的都知道,这个表示引用的其他文件以便告诉编译器你所调用的方法来自哪,等同于Java中的Import。

extern "C"{}

这个宏定义是必须的,他指定extern "C"内部的函数采用C语言的命名风格来编译。否则当JNI采用C++来实现时,由于C和C++编译过程中对函数命名风格不同,将导致JNI在链接时无法根据函数名查找到具体的函数,调用的时候会抛出异常(No implementation found)。

JNIEXPORT jstring JNICALL Java_com_android_peter_jnidemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject instance) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
  • JNIEXPORT 和JNICALL :JNI中定义的宏,可以在jni.h文件中查找到。

  • jstring:参数类型,对应于Java中的string类型,Java和JNI中数据类型对应关系如下:


    Java和JNI数据类型对应关系
  • JNI的数据类型签名:标识一个特定的Java类型,这个类型既可以是类也可以是方法,也可以是数据类型。
    (1)基本数据类型的签名基本都是单词的首字母,但是boolean和long除外,因为B已经被byte占用,而long的表示也被Java类签名占用,所以不同。
    (2)类的签名比较简单,它采用L+包名+类型+;的形式,只需要将其中的.替换为/即可。 例如java.lang.String,它的签名为Ljava/lang/String;,末尾的;也是一部分。
    (3)对象的签名就是对象所属的类签名。
    (4)数组的签名[+类型签名。示例如下:
    char[] [C
    float[] [F
    double[] [D
    long[] [J
    String[] [Ljava/lang/String;
    Object[] [Ljava/lang/Object;
    (5)如果是多维数组那么就根据数组的维度多少来决定[的多少, 例如int[][]那么就是[[I
    (6)方法的签名为(参数类型签名)+返回值类型签名,参数类型的签名是连在一起。
    举个例子,有方法boolean fun(int a, double b, int[] c)。那么按照方法的签名规则就是(ID[I)Z
    例如方法:void fun(int a, String s, int[] c), 那么签名就是(ILjava/lang/String;[I)V
    例如方法:int fun(), 对应签名()I
    例如方法:int fun(float f), 对应签名(F)I

  • 方法名Java_com_android_peter_jnidemo_MainActivity_stringFromJNI:需要遵循Java_包名_调用类类名_Java需要调用的方法名。

  • JNIEnv *env:是结构体JNINativeInterface的二级指针,重定义了大量的函数指针,这些函数指针在jni开发中很常用。可以理解为JNI的上下文环境,需要通过env调用各种接口。

  • jobject instance:表示Java对象中的this。

std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());

函数里面具体的实现逻辑,定义了一个String类型的变量并赋值,然后通过JNI提供的NewStringUTF方法转换成jstring类型并返回。常用的JNI方法可以看一下《JNI学习积累之一 ---- 常用函数大全》里面的介绍。
分析完Android Studio提供给我们的Hello world示例,接下来尝试实现自己的方法以及调用.so文件中的方法。

实现JNI方法

本示例在之前Hello world的基础上实现自己的JNI方法。
1、在cpp目录下新建一个cpp文件——my-native-lib.cpp。


my-native-lib.cpp

2、在CMakeLists.txt文件中添加相应的配置。


配置CMakeLists

3、在需要使用JNI的java类中静态加载对应的lib库(这一步自动创建的示例已经帮们做了)。

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

4、在需要使用JNI的java类中声明新方法


声明新方法

5、在my-native-lib.cpp文件中实现addFromJNI方法

extern "C"
JNIEXPORT jint JNICALL Java_com_android_peter_jnidemo_MainActivity_addFromJNI(JNIEnv *env, jobject instance, jint x, jint y) {
    jint total;
    total = x + y;

    return total;
}

6、调用JNI中新的方法


调用JNI中的新方法

通过Android Studio的Build->Analyze APK可以看到编译生成的.so文件被自动打包到了APK中去。


.so文件被打包到APK中

输出的log如下:


生成的.so文件可以在app->build->intermediates->cmake中对应CPU文件夹下面找到。


生成的.so文件

JNI调用Java方法的流程

JNI调用Java方法的流程是先通过类名或是类的实例找到类,然后再根据方法名找到方法的id,最后就可以调用这个方法了。

JNI方法调用Java类静态方法

(1)在java中定义一个静态方法供JNI调用

    public static void methodCalledByJni(String msgFromJni){
        Log.d("JNIDemo", "methodCalledByJni,msg: " + msgFromJni);
    }

(2)在Java中声明JNI中native方法

public native void callJavaStaticMethodFromJNI();

(3)在cpp文件中实现对应的native方法

extern "C"
JNIEXPORT void JNICALL
Java_com_android_peter_jnidemo_MainActivity_callJavaStaticMethodFromJNI(JNIEnv *env, jobject instance) {
    // 1、获得实例对应的class类
    jclass clazz = env -> GetObjectClass(instance);
    if(clazz == NULL) {
        printf("get object fail!");
        return;
    }
    // 2、通过class类找到对应的method id
    jmethodID methodId = env -> GetStaticMethodID(clazz,"staticMethodCalledByJni","(Ljava/lang/String;)V");
    if(methodId == NULL) {
        printf("get methodId fail!");
        return;
    }
    // 定义一个string作为参数传递给Java方法
    jstring msg = env -> NewStringUTF("msg send by callJavaStaticMethodFromJNI in my-native-lib.cpp .");
    // 3、调用java类中的静态方法
    env -> CallStaticVoidMethod(clazz,methodId,msg);
}

(4)在Java中调用

callJavaStaticMethodFromJNI();

运行输出的log如下:


JNI方法调用Java类非静态方法

有如下两种方式来实现。

  • 通过JNIEnv提供的GetObjectClass方式实现

(1)在Java中定义一个非静态方法供JNI调用

public void publicMethodCalledByJni(String msgFromJni){
    Log.d(TAG, "publicMethodCalledByJni , msg: " + msgFromJni);
}

(2)在Java中声明JNI的native方法

public native void callJavaPublicMethodFromJNI();

(3)在cpp文件中实现对应的native方法

extern "C"
JNIEXPORT void JNICALL
Java_com_android_peter_jnidemo_MainActivity_callJavaPublicMethodFromJNI(JNIEnv *env, jobject instance) {
    //1.获得实例对应的class类
    jclass clazz = env -> GetObjectClass(instance);
    if(clazz == NULL) {
        printf("get object fail!");
        return;
    }
    //2.通过class类找到对应的method id
    jmethodID methodId = env -> GetMethodID(clazz,"publicMethodCalledByJni","(Ljava/lang/String;)V");
    if(methodId == NULL) {
        printf("get methodId fail!");
        return;
    }
    // 定义一个string作为参数传递给Java方法
    jstring msg = env -> NewStringUTF("msg send by callJavaPublicMethodFromJNI in my-native-lib.cpp .");
    //3.调用java类中的方法
    env -> CallVoidMethod(instance,methodId,msg);
}

(4)在Java中调用

callJavaPublicMethodFromJNI();

运行输出的log如下:


  • 通过JNIEnv提供的FindClass方式实现

(1)在Java中定义一个非静态方法供JNI调用

public void publicMethodCalledByJni(String msgFromJni){
        Log.d(TAG, "publicMethodCalledByJni , msg: " + msgFromJni);
    }

(2)在Java中声明JNI的native方法

public native void callJavaMethodByFindClassFromJNI();

(3)在cpp文件中实现对应的native方法

extern "C"
JNIEXPORT void JNICALL
Java_com_android_peter_jnidemo_MainActivity_callJavaMethodByFindClassFromJNI(JNIEnv *env, jobject instance) {
    //1.通过反射获得父类的class类
    jclass clazz = env -> FindClass("com/android/peter/jnidemo/MainActivity");
    if(clazz == NULL) {
        printf("get object fail!");
        return;
    }
    //2.通过class类找到对应的method id
    jmethodID methodId = env -> GetMethodID(clazz,"publicMethodCalledByJni","(Ljava/lang/String;)V");
    if(methodId == NULL) {
        printf("get methodId fail!");
        return;
    }
    // 定义一个string作为参数传递给Java方法
    jstring msg = env -> NewStringUTF("msg send by callJavaMethodByFindClassFromJNI in my-native-lib.cpp .");
    //3.调用java类中的方法
    env -> CallVoidMethod(instance,methodId,msg);
}

(4)在Java中调用

callJavaMethodByFindClassFromJNI();

运行输出的log如下:


通过上面的示例可以看出,JNI方法调用Java类的流程上都是一样的,只是native方法在实现上有细微的差别,调用的JNI提供的方法不同而已。

小结

本文在前文的基础上,通过对Android Studio自动生成的Hello world示例的分析,对JNI开发的流程和相关基础知识进行了梳理。

Demo

参考文献

相关文章

网友评论

      本文标题:JNI学习总结(实践篇)

      本文链接:https://www.haomeiwen.com/subject/htnaxftx.html