美文网首页android NDK
Android NDK 编程指南(二)

Android NDK 编程指南(二)

作者: Sivin | 来源:发表于2018-02-20 00:54 被阅读16次

文章测试案例提交到Github:learnNdk

有了第一篇内容的基础之后,我们开始正式学习JNI。如果前面一片文章你已经写出来了一个demo,但是还有很多有疑问的地方,没关系,你可以把你的疑问记下来,在接下来的学习中,我们将会慢慢解开这些疑问。首先我们看一张图。

JNI.png

这张图很明晰的表达出了JNINDK编程中所担任的角色,以及JNI在和java虚拟机的关系。很显然它属于java虚拟机的一部分。

我们知道在Java中一般有两种类型的方法,一个是instance方法,一个是类方法,在JNI对应的函数里面一般至少都会有两个参数,一个是JNIEnv,一个是jobject或者jclass,其中第二个参数的不同,就是对应着Java中的方法是所属于某个对象还是所属于这个类。这两个参数会在JNIEnv方法调用的时候有些地方会用到。

我们使用NDK编程的目的其实就是为了用C/C++代码来帮助我们实现java里不好实现或者不方便实现的内容,问题说的通俗一点NDK编程其实就是java如何与C/++进行数据通信。

通过上图我们了解到,java想要和C/C++进行数据通信,需要经过JNI层进行桥梁转换,也就是说我们的java层数据想要传递到C/C++层,首先要经过JNI层转换后才能到C/C++层。同理,C/C++层数据想要传递给java层也是如此。

不同语言层级之间进行数据交互,必然涉及到数据类型的转换。不对等的数据类型是无法进行数据交互的,即使可以,也容易导致bug甚至错误的发生。

下面我们就来看看这个三个层级之间数据类型是如何转换的。

基本数据类型转换

我们知道在java里数据类型分成:基本数据类型引用数据类型
基本数据类型有8种分别是:boolean,byte,char,short,int,long,float,double
三者的对应关系如下表:

JavaType JNIType C/C++ Type
boolean jboolean uint8_t(unsigned char)
byte jbyte int8_t (signed char)
char jchar uint16_t (unsigned short)
short jshort int16_t (short)
int jint int32_t (int)
long jlong int64_t (long)
float jfloat float
double jdouble double

上面的基本类型数据在同等对应之间是可以直接转换的,举一个例子:我们以int类型进行举例,其他类型类比如此就行了,还是我们的add函数。
java中的原型:

public native int add(int a ,int b);

这个java方法向JNI层传递了两个int类型的参数,同时需要从JNI层,返回一个int类型的参数。这个参数传递到JNI层是怎样转换的呢?记住,我们并不能直接传递到C/C++层,总是从Java->JNI->C/C++。虽然很多JNI的代码放在C/C++文件,但是这部分代码却属于JNI层。
接下来我们就来看看JNI层的代码

JNIEXPORT jint JNICALL
Java_com_sivin_ndkdemo_NormalJni_add(JNIEnv *env, jobject instance, jint a ,jint b) 

从个函数中我们发现int类型的参数转成了jint类型的参数,同时返回的类型也是jint类型。

那么如何在将jni层的数据传递geiC/C++层呢,我们来看看代码实现

extern "C"
int add(int a ,int b){
    return a+b;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_sivin_opengles_1andorid_MainActivity_add(JNIEnv *env, jobject instance, jint a,
                                                          jint b) {
    jint sum = add(a,b);
    return sum;
}

为了明显的突出三个层级之间的关系,我们特意在native文件里写了一个C语言实现的函数,从函数中我们可以看到jint类型直接转换成C层的int类型,反之亦然,C层的返回的int类型也直接转换成jint然后又返回给了java层的int类型。

我们想强调的一点是上面的类型转是java基本数据类型才能这样做,其他的数据类型是如何转换的呢?

引用数据类型转换

清楚java语言的都知道在java中,除了基本数据类型,剩下就是引用数据类型:

Refrenece 数据类型

Java引用数据类型并不像原始数据类型一样转换到JNI层之后可以直接被C/C++使用,它需要经过再次的变换,使之可以与C/C++进行数据交互,JNI提供了一系列的API来帮助我们完成这些变换,这些API通过JNIEnv获取,并调用。

JAVA中的都有哪些引用数据类型呢?

  • object
  • class
  • throwable
  • String
  • Arrays
  • NIO Buffers
  • Fields
  • Methods

上面我们可以完全用object代替所有,但是这里我们并不打算这样做,上面的object仅代表普通的java对象。因为在JNI中不同的引用数据类型对应着不同的JNI数据类型。具体对应我们看下表:

JNI引用数据类型对应表

JavaType JNIType
java.lang.Class jclass
java.lang.Throwable jthrwoable
java.lang.String jstring
other objects jobject
object[ ] jobjectArray
基本数据类型[ ] (例如:int[ ]) j基本数据类型Array (例如:jintArray)
other arrays jarray

看上面的这个表,不懂的人看的是一头雾水,最显而易见的疑惑是,怎么没有C/C++对应关系。没关系,我们下面的学习就知道了。上面的这个表我们就大致看一下,有一个整体感知就行了,下面我们就来具体的针对每一个对应关系进行解释说明。

首先我们就来从最基本的String类型说起,有人怕是要问,为什么从String而不是object。我想说问的好,解释一下,很简单因为它常用而且特殊,同时JNIString也提供专有的数据类型映射和一些处理函数。后面学习,我们会知道普通的object类型的映射还需要用到其他的知识,而string则相对更集中一些,同时处理字符串应该是每一个编程语言的一个很重要的任务。因此我们首先从String类说起。

String 操作

首先我们回顾一下java String类的一些基本知识,首先java.lang.String类使用了final修饰,不能被继承。即双引号括起的字符串,如"abc",都是作为String类的实例实现的。String是常量,其对象一旦构造就不能再被改变,换句话说,String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创造了一个全新的String对象,而最初的String对象则丝毫未动。String对象具有只读特性,指向它的任何引用都不可能改变它的值,因此,也不会对其他的引用有什么影响。但是字符串引用可以重新赋值。

基本的java String的一些基础知识我们就说这么多,如果对这方面还有疑问的建议好好复习一下这方面的知识。

我们通过上面的表可以知道java中的String类型的数据传递到JNI层后,就转变成了jstring数据类型。但是有一个问题,我们并不知道jstring数据类型该如何在C/C++中处理,记住我们的主线,总是java层--> JNI层-->C/C++层,然后在反过来。那么jstring是如何传递到C/C++层的呢?

我们知道在C里面我们处理字符串使用过char *或者char [ ],当然在C++里面还有string类,这些可以向基本类型一样直接进行转换吗?答案当然是不能。

既然不能直接转换,肯定有转换的方式,否则我们就没法继续编程了。是的,在前面我们提到过,每一个JNI函数都有一个JNIEnv *的参数。这个参数里可以得到很多函数指针,通过这些函数,我们就可以让JavaC/C++进行数据通信。

因为JavaString对象是不可变的,因此JNI并不提供任何修改Java中已经存在String类型数据内容的方法。

JNI同样支持UnicodeUTF-8编码的String,并且提供了两组方法集,来处理这些编码的字符串.
我们来看看那个方法能将jstring-->转换到C/C++层可用的数据,我们打开jni.h头文件,找到JNIEnv结构体。看看里面有没有相关的方法,怎么找?很简单,想要将jstring变换到C/C++一定是通过一个函数,我们先看返回值,看看有没有返回char *相关的函数:
找了一圈,我们发现了下面这个相关的函数。

//将jvm内Unicode字符转换成UTF-8的字符串
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);

在解释这个函数之前,我们先来补充一个知识点,在java中字符串在内存中是采用unicode编码方式存放的:任何一个字符对应两个字节的定长编码。即任何一个字符(无论中文还是英文)都算一个字符长度,占用两个字节。。UTF-8字符串使用一种向上兼容7-bit ASCII字符串的编码协议。UTF-8字符串很像NULL结尾的C字符串,在包含非ASCII字符的时候依然如此。所有的7-bitASCII字符的值都在1~127之间,这些值在UTF-8编码中保持原样。一个字节如果最高位被设置了,意味着这是一个多字节字符(16-bitUnicode值)。

一般情况下我们使用GetStringUTFChars函数进行转换成C的字符串,细心的你可能在寻找的时候还会发现另外一个函数GetStringChars(this, string, isCopy),并且看到它的的返回值是jchar类型,这个函数返回的字符是Unicode编码的,一个字符占用两个字节,对应到C/C++就是short,对应到jni就是jchar由于jchar是一个16位的short类型,无法直接转换成C类型的字符串。因此我们一般不使用这个函数。

/**
*jstring:从java层传递转换过来的string类型数据
*jbooean:表示当我们调用这个函数时将jstring转成成C字符串,是内存的直接指向还是,复制了一份,这里我们一般不关心它是怎么来的,因此我们一般在开发的过程中可以直接传递一个NULL或者nullptr
*/
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);

需要注意一点是,调用这个函数我们将会得到一个C/C++可以处理的字符串,究竟这个函数是将字符串的值复制一份到C/C++,还是直接将内存地址指向,是由java虚拟机实现机制决定的,但是作为开发者,我们应该遵循一个规则我们总应该认为他是复制一份数据到C/C++,这样做至少不会有错。既然我们认为是复制一份数据到C/C++,那么这份内存空间就应该需要我们自己管理了,否则可能会引发内存泄露,因此我们在不需要这份内存数据之后应该将这份内存释放掉。

如何释放内存呢,是不是像C/C++一样使用free或者delete呢?因为我们不清楚从JNIC/C++是如何转换的,因此如果直接使用free显然是不合适的,同样JNI为我们提供相关的释放这段字符串内存的方法。

/**
*jstring :是从java层传递过来的jstring
*char * :由jstring转换成的字符串
ReleaseStringUTFChars(jstring ,const char *);

转换和释放都已经说完了,剩下的我们就可以利用C/C++相关的东西来处理我们的业务了。在处理完成之后,我们想将我们处理的结果在返回给java层。这一步如何实现呢?

显然我们需要将char *数据转换成jstring然后JNI就会将jstring传递到java层转换成String

我们知道在javaString实例在Java中可以通过new 的方式被实例化出来,那么在JNI层中是否有方式也能创造一个jstring然后传递到java层呢?显然是有的,同样我们可以查阅JNIEnv *,w其中NewString和一个NewStringUTF函数,这个两个函数的区别和上面说的一样,这里我们使用newStringUTF这个函数,同样有一个问题,我们可以用C/C++分配的char *来创造jstring对象,那么这个块内存空间是否需要释放呢?是return前释放还是return后释放呢?显然我们不能在return前释放,因为释放了内存,我们java层如何处理呢?。在return后释放?,这个更不现实了,这段代码就不会执行。那么该怎么办呢?答案是,不用管理这块内存,因为我们转成jstring之后传递给了java虚拟机,这块内存空间就由java虚拟机自己管理了。

示例代码如下:

jstring javaString;
javaString = (*env)->NewStringUTF(env, "Hello World!");
return javaString

这个方法传入一个c类型的字符串,返回一个Java类型的字符,由于可能会由于内存空间不足,因此,这个函数将会返回NULL阻止Native code继续运行,同时会抛出一个异常.

这样我们就把一个字符串处理流程就讲解完了,当然还有很多其他的细节我们没有讲到,如unicode字符串等,这里我们后续在补充,因为它还涉及到字符编码问题。这里我们知道有这么回事就行了。但是这已经可以满足我们常规的处理需求了。

相关文章

网友评论

    本文标题:Android NDK 编程指南(二)

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