Android NDK开发之从Java与C互调中详解JNI使用(一)

生活html

这一个礼拜过得真的是苦不堪言,上周由于打球脚踝直接扭伤,肿的想猪蹄同样,而后休息几天消肿了,能够缓慢龟速的行走了,然而五一回来上班第一天,上班鞋子还能穿上,下班脚已插不进鞋子里面了,好吧,又肿回来了,苦逼。java

正文python

回到正文,上篇咱们已学习到了Android NDK开发之从环境搭建到Demo级十步流,主题是DNK环境搭建和Demo示例开发步骤,而今天咱们要学习的是经过JNI实现Java和C之间的交互。c++

对于JNI的理解,上一节也已讲过,这里在回顾下:数组

JNI:Java Native Interface 也就是java本地接口,它是一个协议,这个协议用来沟通java代码和本地代码(c/c++)。经过这个协议,Java类的某些方法可使用原生实现,同时让它们能够像普通的Java方法同样被调用和使用,而原生方法也可使用Java对象,调用和使用Java方法。也就是说,使用JNI这种协议能够实现:java代码调用c/c++代码,而c/c++代码也能够调用java代码。微信

接下来就以一个登陆实例来详细的讲解使用JNI来完成Java与C代码之间的交互。函数

Java 调用 C 本地方法

JNI使用演示

1、首先先构造登陆界面学习

登陆界面中有三个文本输入框,分别对应用户名,密码,验证码。详情以下gradle

2、在Java2CJNI类中添加咱们登陆的login本地方法,且方法中带有三个不一样类型的参数ui

3、使用javah生成头文件,并存放在jni文件夹下,且在本地方法中实现咱们的登陆逻辑(具体的逻辑将会在下面的JNI使用中详细讲解)

4、在咱们的MainActivity中调用本地方法

5、来看执行结果

上述的执行过程及结果,逻辑是在C代码中完成的,主要逻辑很是的简单,就是判断验证码和用户名是否相同,不一样返回错误信息,相同则是登陆成功。

上面所完成的是咱们接下来要讲解JNI的基础前提,因此并无详细的讲解,若是伙伴们有疑问或是不明白的地方请移步到Android NDK开发之从环境搭建到Demo级十步流 来先学习了先基础的知识,由于本次讲解是在上一节的基础上进行的。

OK,如今咱们来详细的看下在Java2C.c中咱们是怎么使用JNI来完成调用Java调用C的。

JNIEXPORT jstring JNICALL Java_com_sanhui_ndkdemo_Java2CJNI_login
(JNIEnv *env, jobject jobj, jstring jUserName, jstring jPSW, jint jcode){
    const char* resultMessage;
    if(jcode == 1234){
        const char* cStr;
        jboolean isCopy;
        cStr = (*env)->GetStringUTFChars(env, jUserName,&isCopy);
        int result = strcmp(cStr, "guanmanman");
        if(result == 0){
            //一样的道理psw也能够作同样的验证
            //TODO
            resultMessage = "success login !";
        }else{
            resultMessage = "error username";
        }
    }else{
        resultMessage = "error code";
    }
    return (*env)->NewStringUTF(env, resultMessage);
};

JNI方法声明

咱们知道经过javah会为咱们生成这样的头文件方法(补全后):

JNIEXPORT jstring JNICALL Java_com_sanhui_ndkdemo_Java2CJNI_login
(JNIEnv *env, jobject jobj, jstring jUserName, jstring jPSW, jint jcode){}

这里作下解释:

①:咱们知道在调用本地方法时会返回一个String类型的返回值, jstring 就是JNI对应Java的一个返回类型。
②:经过Java_ + 包名(com.sanhui.ndkdemo) + 类名(Java2CJNI) + 方法名(login)的形式生成在原生代码中所识别的方法,当java虚拟机调用com.sanhui.ndkdemo.Java2CJNI.login的方法时会自动查找到这个C实现的Native函数并调用。
③:参数列表中包含五个参数,其中三个是经过java调用传递的,这个很少说,JNIEnv 是指向可用JNI函数表的接口指针,也就是说经过它能够调用JNI所封装的函数进行处理逻辑业务,jobject 则是Java类中的对象引用,这里指的是Java2CJNI类的实例,也就是说,当调用本地方法时,JNI将会自动的获取当前类的实例,以方便原生代码使用。

JNI 数据类型

原生数据类型同Java同样,都包含两类,基本数据类型与引用类型。

JNI 基础数据类型

在JNI当中,基本数据类型能够直接与C/C++的相对应的基本数据类型映射,因此咱们能够直接拿来使用并不须要转换。

咱们先来看看java、JNI、C之间的基本类型的映射关系:

java类型 JNI类型 C类型
Boolean jboolean unsigned char
Byte jbyte char
Char jchar unsigned short
Short jshort short
Int jint int
Long jlong long long
Float jfloat float
Double jdouble double

经过上面讲解和表格咱们如今能够明白为何咱们经过本地方法传递进来的整形验证码,使用JNI技术转化为jint类型时能够直接的进行以下判断:

if(jcode == 1234){
}

JNI 引用类型-字符串

引用类型咱们并不能直接的使用,它不是以原生数据类型的形式展示,而是须要经过JNI提供的一组相关的API把引用类型提供给原生代码使用。

java类型 JNI类型
java.lang.Class jclass
java.lang.Throwable jthrowale
java.lang.String jstring
byte[] jbyteArray
boolean[] jbooleanArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray

上面也说过引用类型不可以直接的被原生代码使用,它须要经过JNI提供的API来提供给原生使用,例如:

const char* cStr;
        jboolean isCopy;
        cStr = (*env)->GetStringUTFChars(env, jUserName,&isCopy);
        int result = strcmp(cStr, "guanmanman");

上面的代码中,GetStringUTFChars就是JNI所提供的API,经过该方法能够把java中所传递的Stirng字符串转化为原生代码所能识别的字符串,而后在进行下面的比较操做。strcmp方法是C语言中所提供的的比较字符串的方法。

说到这里,咱们在来详细的看下JNI针对字符串的操做:

①:建立字符串

能够在原生代码中使用NewString函数构建Unicode编码风格的字符串实例,假如你使用的是utf-8的编码格式,可使用NewStringUTF函数来构建,如:

jstring jString = (*env)->NewString(env,"I am from C");
    jstring jString = (*env)->NewStringUTF(env,"I am from C");

②:把Java字符串转换成C字符串

Java字符串String是属于引用类型的,它不可以直接的被原生代码使用,为了在原生代码中使用Java传递过来的String串,咱们须要借用JNI API,根据编码的不一样,分别能够是用GetStringChars和GetStringUTFChars函数进行转换,如咱们上面的实例:

const char* cStr = (*env)->GetStringUTFChars(env, jUserName,&isCopy);

isCopy参数是可选的,它是jboolean类型的,它的调用者明确返回的C字符串地址是指向副本黑市指向堆中的对象。

JNI 引用类型-数组

在引用类型中还有一种比较重要的类型,那就是数组,JNI中也提供了相应的API来处理和使用数组。

①:建立数组

JNI经过New<?>Array函数在原生代码中建立数组,<?>类型能够是byte,short,int等类型,如:

jintArray intArray = (*env)->NewIntArray(env,5);

NewIntArray数组中应该给出明确的数组长度,例如,5就是intArray的数组长度。

②:对数组的操做
如以上介绍,数组属于引用类型,经过Java传递过来的数组咱们是没法直接进行操做的,可是根据JNI提供的API咱们能够很顺利的进行数组的操做,如:

//获取数组长度
    //jsize GetArrayLength(this, array);
     //获取数组元素
    //jint* GetIntArrayElements(this, array, isCopy);

不只仅能够对数组进行直接的操做,并且还能够对数组的副本进行操做,如

经过Get<?>ArrayRegion函数将给定的java数组复制成C数组,而后咱们就能够在原生代码中针对C数组进行操做。

操做完成以前咱们还能够经过如下函数把数组的副本给还原到原来的Java数组中,

经过以上的函数咱们就完成了在原生代码中对Java数组的操做。

③:数组操做实例

首先,在Java2CJNI类中添加一个本地方法modifyValue,接受一个int类型的数组,完成后从新经过javah生成.h头文件,如:

而后在Java2C.c中完成对它的逻辑操做:

代码比较简单,就是经过JNI API获取数组的长度、元素,而后遍历数组针对每一个元素进行加上100,完成后把C数组还原给Java数组,代码注释的已很清楚的体现。

最后在MainActivity中调用modifyValue方法,来看下执行过程和结果。

OK,看了下篇幅,没想到这么长了,本来打算把Java调C和C调Java一块儿讲解下的,看来C调Java只能放到下一篇进行讲解了。

下篇主题预告:原生C访问Java成员、调用Java方法的JNI使用,以及使用gradle-experimental来调试原生代码。

那么,今天就先讲解到这里吧。请关注微信公众号。谢谢

更多资讯请关注微信平台,有博客更新会及时通知。爱学习爱技术。

相关文章
相关标签/搜索