JNI学习总结(实践篇)

前言

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

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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