libname java jni,android NDK开发之JNI操作JAVA

本篇为android NDK开发的第二部分JNI,这是NDK开发的重点,属于纯编码部分,这一部分将分为两节:第一节就是本篇要讲述的JNI操作java,第二节讲述JNI调用纯C,也就是非标准JNI的so库。

JNI基础知识

1.认识JNI函数

创建一个NDK项目

6f2b6080e84a

image.png

其中,native-lib.cpp就是写JNI的地方,CMakeLists.txt就是配置。先来看一下native-lib.cpp

extern "C"

JNIEXPORT jstring JNICALL

Java_cn_mmdet_jean_Test_helloWord(JNIEnv *env, jobject instance) {

std::string hello = "Hello from C++";

return env->NewStringUTF(hello.c_str());

}

解释说明:

【extern "C"】 :为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的,如果是C++文件(.CPP),就需要写这么一句。如果是C文件(.C)可写可不写。

【JNIEXPORT jstring JNICALL】:JNIEXPORT 和JNICALL是宏定义,被定义在jni.h里,固定写法。jstring 是函数返回类型。jstring 就相当于Sring.

【Java_cn_mmdet_jean_Test_helloWord】:这个是函数名,对应着本地方法,规则是:

Java开头,跟上包名+类名+方法名。所以可知,这个JNI函数对应着Test类中的helloWord方法,而Test位于包cn.mmdet.jean下。

【JNIEnv *env, jobject instance】这个是函数参数,固定的,每个JNI函数应该都有这两个参数,并且位置在第一和第二。env可以看做是Jni接口本身的一个对象,是一个指针,基本上JNI的所有操作都是基于env的,后面我们将会看到,指针是c语言具有的特性。instance表示 该JNI函数对应的本地方法所在的类。

2.本地方法

本地方法就是java层能够调用的函数,用native修饰,没有方法体,只有声明,对应着cpp文件里的JNI 函数。下面我们来创建一个对应helloWord的本地方法:

public class Test {

static {

System.loadLibrary("native-lib");

}

public native String helloWord();

}

说明:

新建一个Test类,加载本地函数库 System.loadLibrary,我的叫native-lib,与CMakeLists中定义的要一致,我的配置如下。

add_library(

native-lib

SHARED

src/main/cpp/native-lib.cpp )

target_link_libraries(

native-lib

${log-lib} )

其中

public native String helloWord();

表示本地方法,对应着native-lib.cpp中Java_cn_mmdet_jean_Test_helloWord(JNIEnv *env, jobject instance),这里的instance就是Test类的实例。

2.带参数的本地方法

定义一个带参数的本地方法,如下:

public native void helloword(String from,int count);

对应的JNI函数方法如下:

extern "C"

JNIEXPORT void JNICALL

Java_cn_mmdet_jean_Test_helloword(JNIEnv *env, jobject instance, jstring from_, jint count) {

const char *from = env->GetStringUTFChars(from_, 0);

env->ReleaseStringUTFChars(from_, from);

}

快捷方式:像上面这么一行行敲,不仅累还容易出错。可以先定义好native方法,鼠标定位到方法前,使用ctrl+enter,选择第一项,IDE会自动帮我们创建好对应的JNI函数。

6f2b6080e84a

image.png

经过,上面相信你已经熟悉了JNI函数、本地方法了。

对于这中跨语言编程,最重要的最繁琐的也就是数据类型转换了,但是JNI已经帮我们封装好了。下面我们就来看一下数据类型的映射。

数据类型

在JNI里也存在类似的数据类型,与Java比较起来,其范围更具严格性,分为基本数据类型,如:int、 float 、char等基本类型,引用类型,如:类、实例、数组。用一张表格来表示应该会更清晰:

基本类型

Java

JNI

C/C++

boolean

jboolean

C/C++8位整型 unsigned char

byte

jbyte

C/C++带符号的8位整型 char

char

jchar

C/C++无符号的16位整型 unsigned short

short

jshort

C/C++带符号的16位整型short

int

jint

C/C++带符号的32位整型 int

long

jlong

C/C++带符号的64位整型e long

float

jfloat

C/C++32位浮点型 float

double

jdouble

C/C++64位浮点型 double

Object

jobject

任何Java对象

Class

jclass

Class对象

String

jstring

字符串对象

Object[]

jobjectArray

任何对象的数组

boolean[]

jbooleanArray

布尔型数组

byte[]

jbyteArray

比特型数组

char[]

jcharArray

字符型数组

short[]

jshortArray

短整型数组

int[]

jintArray

整型数组

long[]

jlongArray

长整型数组

float[]

jfloatArray

浮点型数组

double[]

jdoubleArray

双浮点型数组

了解完JNI的基本知识与数据类型转换,下面就是JNI操作Java的实例了。

JNI操作Java

这一部分主要讲解在JNI中调用Java类的方法,属性、实例化类等,分别以java代码、本地方法、JNI方法进行展示。

属性

java代码 定义一个属性name

public class Test{

static {

System.loadLibrary("native-lib");

}

public String name = "jean";

}

本地方法(后面将以native代替)

public class Test {

...

...

public native String getTestName();

}

JNI

extern "C"

JNIEXPORT jstring JNICALL

Java_cn_mmdet_jean_Test_getTestName(JNIEnv *env, jobject instance) {

//通过instance获取jclass对象,这里相当于Test

jclass _jcalss = env->GetObjectClass(instance);

//通过GetFieldID获取FieldID

//GetFieldID方法参数分别为:jclass对象、jclass对象属性名、签名(数据类型)

jfieldID _jFieldId = env->GetFieldID(_jcalss,"name","Ljava/lang/String;");

//通过FieldID获取属性的值

jstring _jstring = (jstring)env->GetObjectField(instance,_jFieldId);

return _jstring ;

}

说明:

GetFieldID方法中第三个参数为签名,对应着属性的数据类型,

如何填写,请参考下面:

Java

签名

boolean

Z

byte

B

char

C

short

S

int

I

long

J

float

F

double

D

全限定的类

L +class +;(注意这里的分号不能少)

数组 type[]

[ + type

函数

(arg-type)return type

举例说明:

String name

Ljava/lang/String;

int test(int a,String b,byte[] c)

(ILjava/lang/String;[B)

相信你已经会使用签名了,不会用也没关系,接下来我们还会用到哟,多练习几次就懂了。

方法

java

public class Test {

static {

System.loadLibrary("native-lib");

}

//Java

public String getName(){

return "1234";

}

//native

public native String getNameFromJava();

}

JNI

extern "C"

JNIEXPORT jstring JNICALL

Java_cn_mmdet_jean_Test_getNameFromJava(JNIEnv *env, jobject instance) {

//获取jlass对象

jclass _jclass = env->GetObjectClass(instance);

//获取 方法ID GetMethodID

//参数说明:jlass对象、函数名、签名,这里签名有一个(),这就是方法的签名

jmethodID _jmethodID = env->GetMethodID(_jclass,"getName","()Ljava/lang/String;");

//通过MethodID调用方法,使用CallObjectMethod函数

jstring _jstring = (jstring)env->CallObjectMethod(instance,_jmethodID);

//将字符串转换为C语言的字符串 char*

char* tempStr = (char*)env->GetStringUTFChars(_jstring,NULL);

char text[20] = "success";

char* finResult = strcat(tempStr,text);

//将C语言字符串转为jstring返回

return env->NewStringUTF(finResult);

}

方法签名说明:

方法签名为(arg-type)return type

举例说明:

public native String getNameFromJava();

//方法返回值类型为String所以签名为Ljava/lang/String;,参数类型为空,所以不填()

()Ljava/lang/String;

public native byte[] getNameFromJava(String o);

//方法返回值类型为byte[] 所以签名为[B;,参数类型为String所以参数签名为Ljava/lang/String;

(Ljava/lang/String;)[B

构造函数

这里主要演示一下,JNI中如何调用一个类的构造函数,实例化一个类的对象 返回去。

java

//定义一个类

public class Person {

private String name;

private int age;

private boolean isUp;//是否成年

public Person(String name, int age, boolean isUp) {

this.name = name;

this.age = age;

this.isUp = isUp;

}

}

//native

public class Test {

static {

System.loadLibrary("native-lib");

}

//native

public native Person genPerson(String name,int age,boolean isUp);

}

JNI

extern "C"

JNIEXPORT jobject JNICALL

Java_cn_mmdet_jean_Test_genPerson(JNIEnv *env, jobject instance, jstring name_, jint age,

jboolean isUp) {

const char *name = env->GetStringUTFChars(name_, 0);

//首先 创建jclass对象,FindClass参数为Person所在的包名,用"/"分割

jclass cls = env->FindClass("cn/mmdet/jean/Person");

/**

* 调用构造函数其中函数名

* 固定为""

* 参数类型分别为String、int、boolean,这里有几个参数,就传几个参数类型的签名

* v表示void,因为构造函数返回值为void

*/

jmethodID methodID = env->GetMethodID(cls, "", "(Ljava/lang/String;IZ)V");

//使用NewObject创建java对象,前两个参数分别为class对象、方法ID,后面为构造函数的实参传入,有几个传几个

jobject object = env->NewObject(cls, methodID,name,age,isUp);

env->ReleaseStringUTFChars(name_, name);

return object;

}

上述JNI中,用了不少env调用的函数,如NewObject、GetMethodID等等,这些都是JNI封装在jni.h里的,我们通过#include 就能使用,主要作用就是Java与C的互相转换。另外JNI部分的代码编写,需要你知道一些C/C++的知识。我们来看一些常用的转换函数:

native

public class Test {

static {

System.loadLibrary("native-lib");

}

private native String testType(String a,int b,byte[] c,String[] d,Person e);

}

然后使用快捷键,创建对应的JNI函数:

extern "C"

JNIEXPORT jstring JNICALL

Java_cn_mmdet_jean_Test_testType(JNIEnv *env, jobject instance, jstring a_, jint b, jbyteArray c_, jobjectArray d, jobject e) {

//将java字符串 转为C的字符串

const char *a = env->GetStringUTFChars(a_, 0);

//将byte数组 转为C能使用的jbyte*

jbyte *c = env->GetByteArrayElements(c_, NULL);

//指针释放

env->ReleaseStringUTFChars(a_, a);

env->ReleaseByteArrayElements(c_, c, 0);

//char* 转jstring

return env->NewStringUTF(returnValue);

}

这里只是简单的转换。

JNI调用Java的知识到这里就结束了。学完你应该会使用了,可能会有些生涩,多练习几遍就对了,慢慢消化。

一些复杂的操作将会在下一节里做讲解,如JNI调用so函数、指针参数、字节数组的返回、二维数组的返回等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值