一.准备工作 1.为eclipse增加c和c++的开发插件(CDT插件)
2.安装NDK开发环境 3.安裝交叉编译工具Cygwin,配置环境变量
二.操作步骤
1.首先在java类中声明一个native的方法
2.使用javah命令生成包含native的方法声明的c/c++头文件
3.按照生成的c/c++头文件来写c/c++源文件
4.将c/c++原文件编译成动态链接库(DLL,so等),在android环境下通过Cygwin来编译
JNI操作过程中要获得方法的签名通过用javap命令获得
数据类型、方法 | 签名 |
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J (注意不是L) |
short | S |
void | V |
boolean | Z (注意不是B) |
类类型 | L跟完整类名,如Ljava/lang/String; (注意,以L开头,要包括报名,以斜杠分隔,最后有一个分号作为类型表达式的结束) |
数组type[] | [type,如果二维数组则为[[type 如,[float [[float |
方法 | (参数类型签名)返回值类型签名 如:float fun(int a, int b),它的签名为(II)F String toString(),它的签名为()Ljava/lang/String; |
JNI函数总结:
JNI开发当中最重要的就是JNIEnv JNIEnv类型实际上代表了Java环境,通过这个JNIEnv*指针,就可以对java端的代码进行操作,例如:创建java类的对象,调用java对象的方法,获取java对象的,代码进行操作.
JNIEnv类中有很多的函数可用:
NewObject/NewString/New<TYPE>Array
Get/Set<TYPE>Field Get/SetStatic<Type>Field
Call<TYPE>Method/CallStatic<TYPE>Method 等很多函数.
java代码既可以访问本地的c/c++代码,同时c/c++代码也可以访问java代码并互相操作
下面对常用函数进行分析:
1.jclass的取得 为了能够在c/c++中使用java类.JNI.h头文件中专门定义了jclass来表示java中的Class类
JNIEnv类中可以取得jclass的函数如下:
jclass FindClass(const char* clsName); //通过传入类完整路径取得类,
jclass GetObjectClass(jobject obj); //通过传入对象名取得其所在的类
jclass GetSuperClass(jclass obj); //通过传入对象名取得其所属类的父类
FindClass会在系统环境变量下寻找类,传入完整的类名,注意包与包之间用'/'分隔而不是'.'
例如: jclass cls_string = env->FindClass("java/long/String");
2.方法java中的属性和方法
JNI在JNI.h头文件中定义了jfieldID和jmethodID类型来代表java中的属性和方法
要对java端的属性和方法进行访问和操作的时候需要先获得该属性的jfieldID和方法的jmethodID
JNIEnv类中取得方法和属性的函数如下:
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) //取得普通的jfieldID,参数1:类名,参数2:属性字段名,参数3:字段的签名()通过javap 命令获得
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)//静态的jfieldID
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)//普通的jmethodID参数1:类名,参数2:属性字段名,参数3:方法的签名()通过javap命令获得
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)//静态的jmethodID
示例代码:
1.通过使用签名取得属性/方法jfieldId/jmethodID
java端:
public class Hello {
public native void test();
public int property;
public int function(int foo,Date date, int[] arr) {
//.......
}
}
test方法在jni本地的实现:
JNI端test的实现:
JNIEXPORT void JNICALL java_Hello_test(JNIEnv* env,jobject obj) {
//由于test不是静态方法,传入的就是调用该方法的对象,若是静态则传入的jclass
jclass hello_clazz = env->GetObjectClass(obj);//取得对象所属的类
jfieldID fieldID_prop = env->GetFieldID(hello_clazz,"property","I");//取得jfieldID
jmethodID methodID_func = env->GetMethodID(hello_clazz,"function",(ILjava/util/Date;[I)I)//取得jMethodID
env->CallIntMethod(obj,methodID_func,0L,null,null);//调用方法
}
3.获取属性并设置属性
取得了相应属性的jfieldID后 就可以通过使用JNI中提供的
Set<TYPE>Field(jobject obj,jfieldID fieldID,相应类型的值)
Get<TYPE>Field(jobject obj,jfieldID fieldID)
SetStatic<TYPE>Field(jclass clazz,jfieldID fieldID,相应类型的值)
GetStatic<TYPE>Field(jclass clazz,jfieldID fieldID) 等函数对不同属性进行操作了,传入的参数为
示例代码:
package cn.itcast; public class TestNative {
public int number = 10;
public native void sayHello();
public static void main(String[] args) {
System.loadLibrary("NaviteCode");
TestNative tst = new TestNative();
tst.sayHello();
System.out.println(tst.number);
}
}
JNI端sayHello的实现:
//通过在jni端设置java中属性的值
JNIEXPORT void JNICALL java_cn_itcast_TestNative_sayHello(JNIEnv* env,jobject obj) {
jclass clazz_TestNative = env->GetObjectClass(obj);//取得對象所属的类
jfieldID id_number = env->GetFieldId(clazz_TestNative,"numver","I");//通过类取得number的jfieldID
jint number = env->GetIntField(obj,id_number);//取得number
env->SetIntField(obj,id_number,100L);//设置number的值
}
4.JNI中对java方法的调用
JNIEnv提供了很多对java方法调用的函数
调用java中实例方法的三种形式:
Call<TYPE>Method(jobject obj,jmethodID MethodID,...);
Call<TYPE>Method(jobject obj,jmethodID MethodID,va_list lst);
Call<TYPE>Method(jobject obj,jmethodID MethodID,jvalue* v);
通常第一种方式最常用,第二种方式需要传入一个参数表的va_list变量,第三种方式需要传入指向一个jvalue或jvalue数组的指针
调用java中的静态方法三种形式:
CallStatic<TYPE>Method(jclass clazz,jmethodID MethodID,...);
CallStatic<TYPE>Method(jclass clazz,jmethodID MethodID,va_list lst);
CallStatic<TYPE>Method(jclass clazz,jmethodID MethodID,jvalue* v);
示例代码: package cn.itcast; public class TestNative {
public native void sayHello();
public double max(double num1,double num2) {
return num1 > num2 ? num1 : num2;
}
public static void main(String[] args) {
System.loadLibrary("NaviteCode");
TestNative tst = new TestNative();
tst.sayHello();
}
}
JNIEXPORT void JNICALL java_cn_itcast_TestNative_sayHello(JNIEnv* env,jobject obj) {
jclass clazz_TestNative = env->GetObjectClass(obj);//取得對象所属的类
jmethodID max_methodID = env->GetMethodID(obj,"max","(DD)D");//取得jmethodID
env->CallDoubleMethod(obj,max_methodID,3.14,3.15);//调用max方法
}
JNI中要想调用父类的方法可以使用CallNonVirtual<TYPE>Method(),使用此方法时必须要取得父类的jclass和父类的方法的jmethodID
5.JNI中对java对象的创建 通过NewObject()函数来创建java对象,使用GetMethodID取得构造函数的jMethodID,若传入的函数名称为"<init>"就能取得构造函数 构造函数的返回值始终为void
示例代码:
package cn.itcast; public class TestNative {
public static native void outputDate();
public static void main(String[] args) {
System.loadLibrary("NaviteCode");
outputDate();//调用本地方法
}
}
JNIEXPORT void JNICALL java_cn_itcast_TestNative_outputDate(JNIEnv* env,jobject obj) {
jcalss clazz_date= env->FindClass("java/util/Date"); //取得Date类的jclass
jmethodID mid_Date_constr = env->GetMethodId(clazz_date,"<init>","()V"); //取得Date类的构造函数
jobject now = env->NewObject(clazz_date,jmethodID_Date_constr );//通過构造函数创建Date类的对象
jmethodID mid_Date_getTime = env->GetMethodID(clazz_date,"getTime","()J");//取得date类中getTime()方法的jmethodID
jlong time = env->CallLongMethod(now,mid_Date_getTime)//调用getTime()方法
}
6.JNI中对java字符串的操作
相关函数:
1.const jchar* GetStringChars(jstring string, jboolean* isCopy);
2.const char* GetStringUTFChars(jstring string, jboolean* isCopy);
3.void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf)
第一个参数是指向java中String對象的jstring变量
第二个参数是一个jboolean的指针,用来标示是否对java中的String进行拷贝,为NULL则不关心拷贝,为JNI_TRUE和JNI_FALSE则说明是否拷贝
这两个函数会有不同的动作:
1.开启新内存,然后把java中的String拷贝到这块内存中,然后返回执行这块内存地址的指针;
2.直接返回指向java中String的内存的指针,此时千万不能改变这个内存中的内容,否则将会破坏String在java中始终是常量这个原则
示例代码:
package cn.itcast; public class TestNative {
public String message = null;
public static native void callCppFunction();
public static void main(String[] args) {
System.loadLibrary("NaviteCode");//加载库文件
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));//取得用戶键盘录入流
String str = reader.readLine();//读取用户输入的字符串
TestNative obj = new TestNative();
obj.message = str;
obj.callCppFunction();//调用本地方法
}
}
JNIEXPORT void JNICALL java_cn_itcast_TestNative_callCppFunction(JNIEnv* env,jobject obj) {
jclass clazz_TestNative = env->GetObjectClass(obj);//取得对象所属的类
fieldID fid_msg = env->GetFieldID(obj,"message","Ljava/long/String;"); //取得message字段的jfieldID
jstring j_msg = (jstring)env->GetObjectField(obj,fid_msg )//取得string字段
const char* jstr = env->GetStringChars(j_msg ,NULL);/取得java端用戶从键盘录入的字符串
//....对字符串进行处理 env->ReleaseStringChars(j_msg,jstr);//释放资源
}
7.JNI中对java数组的操作
数组分为基本类型数组和对象类型数组
相关函数:
基本类型数组:
Get<TYPE>ArrayElements(<TYPE>Array array,jboolean* isCopied);这类函数用于将java中的基本类型数组转化到c/c++中的数组 Release<TYPE>ArrayElements(<TYPE>Array,<TYPE>* array,jint mode);此函数用于如何处理java跟c/c++的数组,
mode可以取下面的值
0 -->对java 的数组进行更新并释放c/c++的数组
JNI_COMMIT -->对java 的数组进行更新但不释放c/c++的数组
JNI_ABORT -->对java 的数组不进行更新但释放c/c++的数组
GetArrayLength(jarray array)用于取得不同类型数组的长度
Get<TYPE>ArrayRegion(<TYPE>Array array,jsize jstart,jsize len,<TYPE>* buf) ,此类函数用于在c/c++中先开辟一块内存把java中基本类型数组拷贝到这块内存中
Set<TYPE>ArrayRegion(<TYPE>Array array,jsize jstart,jsize len,const <TYPE>* buf) 此类函数用于将java中的基本类型数组用c/c++中的数组元素来赋值
对象类型数组:
JNI中没有直接的将java中的对象类型数组转化到c++中的对象类型数组,通过使用Get/SetObjectArrayElement这样的函数对java的object[]数组进行操作
示例代码:
package cn.itcast; public class TestNative {
int [] arrays = {1,4,6,8,5,3,1,5,8,9};
public static native void callCppFunction();
public static void main(String[] args) {
System.loadLibrary("NaviteCode");//加载库文件
TestNative obj = new TestNative();
obj. callCppFunction();
}
}
JNIEXPORT void JNICALL java_cn_itcast_TestNative_callCppFunction(JNIEnv* env,jobject obj) {
jclass clazz_TestNative = env->GetObjectClass(obj);//取得对象所属的类
jfieldID fid_arrays = env->GetFieldID(clazz_TestNative,"arrays","[I");//取得数组字段的jfieldID
jintarray jint_arr (jintarray)env->GetObjectField(obj,fid_arrays);//取得数组对象
jint* int_arr = env->GetIntArrayElements(jint_arr,null);//取得数组的元素
jint len = env->GetArrayLength(jint_arr);//取得数组的长度
for(jsize i =0 ; i<len ;i++) {
//....循环对数组元素进行处理
}
env->ReleaseIntArrayElements(jint_arr,int_arr,JNI_ABORT);//释放c++的数组
}