第一章用JNI实现与原生代码通信
3.1什么是jni
3.2一个简单的示例
示例代码中查看实现步骤:
@@@@@@@@@@A加载共享库@@@@@@@@@@@
static{
System.loadLibrary("hello-jni");
}
@@@@@@@@@@B声明原生方法@@@@@@@@@@
publicnativeStringstringFromJNI();
publicnativeStringunimplementedStringFromJNI();
@@@@@@@@@@@@@@@C调用原生方法@@@@@@@@@@@@@@@@@@@
tv.setText(stringFromJNI());
hello-jni.c中实现原生方法
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv*env,
jobjectthiz)
{
return(*env)->NewStringUTF(env,"HellofromJNI!");
}
c/c++头文件生成器javah
D:\workspace4\HelloJni>javah-classpathbin/classes-bootclasspathd:\tools\andr
oid-sdk-windows\platforms\android-10\android.jar-djnicom.example.hellojni.Hel
loJni
命令格介绍如下
D:\workspace4\HelloJni\jni>javah
用法:
javah[options]<classes>
其中,[options]包括:
-o<file>输出文件(只能使用-d或-o之一)
-d<dir>输出目录
-v-verbose启用详细输出
-h--help-?输出此消息
-version输出版本信息
-jni生成JNI样式的标头文件(默认值)
-force始终写入输出文件
-classpath<path>从中加载类的路径
-bootclasspath<path>从中加载引导类的路径
<classes>是使用其全限定名称指定的
(例如,java.lang.Object)。
在jni目录下生成头文件com_example_hellojni_HelloJni.h,内容如下:
#include<jni.h>
/*Headerforclasscom_example_hellojni_HelloJni*/
#ifndef_Included_com_example_hellojni_HelloJni
#define_Included_com_example_hellojni_HelloJni
#ifdef__cplusplus
extern"C"{
#endif
/*
*Class:com_example_hellojni_HelloJni
*Method:stringFromJNI
*Signature:()Ljava/lang/String;
*/
JNIEXPORTjstringJNICALLJava_com_example_hellojni_HelloJni_stringFromJNI
(JNIEnv*,jobject);
/*
*Class:com_example_hellojni_HelloJni
*Method:unimplementedStringFromJNI
*Signature:()Ljava/lang/String;
*/
JNIEXPORTjstringJNICALLJava_com_example_hellojni_HelloJni_unimplementedStringFromJNI
(JNIEnv*,jobject);
#ifdef__cplusplus
}
#endif
#endif
@@@@@@@@@@可以在eclipse中生成头文件@@@@@@@@@@@@@
Run-àexternaltool-àeternaltoolsconfigurations
选中programe,单击新建newlaunchconfiguration
选中main选项卡:
Name:GenerateCandC++HeaderFile
Location:${system_path:javah}
Workingdirectory:${project_loc}/jni
Arguments:-classpath"${project_classpath};${env_var:ANDROID_SDK_HOME}/platforms/android-14/android.jar"${java_type_name}
选中refresh选项卡:选中refreshresourcesuponcompletionà
theprojectcontainingtheselectedresource
选中common选项卡:选中displayinfavoritesmenu-àexternaltools
保存退出
测试生成头文件:
选中测试目标类文件-àrun-àexternaltool-àGenerateCandC++HeaderFile
会在jni目录下生成头文件
方法声明
JNIEXPORTjstringJNICALLJava_com_example_hellojni_HelloJni_stringFromJNI(
JNIEnv*,//指向jni函数表的接口指针
jobject);//hellojni类的java对象引用
JNIEnv接口指针
原生代码通过jnienv接口指针提供的各种函数来使用虚拟机的功能
return(*env)->NewStringUTF(env,"HellofromJNI!");
实例方法和静态方法
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv*env,
jobjectthiz)
实例方法可以通过第二个参数thiz取得(jobject实例引用)
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv*env,
jclassclazz)
静态方法由于没有和实例绑定,通过第二个参数取得类引用jclass
3.3数据类型
Java基本数据类型
Java类型 | Jni类型 | c/c++类型 | 大小 |
Boolean | Jbollean | Unsignedchar | 无符号8位 |
Byte | Jbyte | Char | 有符号8位 |
Char | Jchar | Unsignedshort | 无符号16位 |
Short | Jshort | Short | 有符号16位 |
Int | Jint | Int | 有符号32位 |
Long | Jlong | Longlong | 有符号64位 |
Float | Jfloat | Float | 32位 |
double | jdouble | double | 64位 |
Java引用类型映射
Java类型 | 原生类型 |
Java.lang.class | Jclass |
Java.lang.throwable | Jthrowable |
Java.lang.string | Jstring |
Otherobjects | Jobjects |
Java.lang.object[] | JobjectsArray |
Boolean[] | jbooleanArray |
Byte[] | jbyteArray |
Char[] | jcharArray |
Short[] | jshortArray |
Int[] | jintArray |
Long[] | jlongArray |
Float[] | jfloatArray |
Double[] | jdoubleArray |
OtherArrays | jarray |
3.4对引用数据类型的操作
3.4.1对字符串操作
创建字符串
示例代码如下:
JstringjavaString;
javaString=(*env)->NewStringUTF(env,“helloworld!”);
把java字符串转换为c字符串
GetStringChars将unicode格式的java字符串转换为c字符串
GetStringUTFChars将utf-8格式的字符串转换为c字符串
注:以上两个函数的三个参数用来确定返回字串是指向副本还是栈中固定对象
示例代码如下:
Constjbyte*str;
JbooleanisCopy;
Str=(*env)->GetStringUTFChar(env,jstring,&isCopy);
If(0!=str){
Printf(“javastringis:%s”,str);
If(JNI_TRUE==isCopy){
Printf(“cstringiscopyofthejavastring!”);
}else{
Printf(“cstringpointstoactualstring!”);
}
}
释放字符串
ReleaseStringChar用来释放unicode格式的字符串
ReleaseStringUTFChar来释放utf-8格式的字符串
示例代码如下:
(*env)->ReleaseStringChar(env,javaString,sstr);
3.4.2数组操作
创建数组New<type>Array
示例代码如下:
jintArraayjavaArray;
javaArray=(*env)->NewIntArray(ent,10);
if(0!=javaArray){
//数组可以使用
}
访问数组
将数组复制成c数组
让jni提供指向数组的指针
对副本的操作
Get<type>ArrayRegion
Set<type>ArrayRegion
//将java数组复制到c数组中
JintnativeArray[10];
(*env)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);
//从c数组向java数组提交所作的修改
(*env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);
对直接指针的操作
Get<type>ArrayElements
Release<type>ArrayElements
//取得指向java数组元素的直接指针
jint*nativeDirectArray;
jbooleanisCopy;
nativeDirectArray=(*env)->GetIntArrayElements(env,javaArray,&isCopy);
//释放指向java数组的直接指针
(*env)->ReleaseIntArrayElements(env,javaArray,nativeDirectArray,0);
注:第四个参数为释放模式
0将内容复制回来并释放原生数组
JNI_COMMIT将内容复制回来但不释放原生数组,一般用于更新数组
JNI_ABORT不将内容复制回来,释放原生数组
3.4.3NIO操作
直接创建字节缓冲NewDirectByteBuffer
示例代码如下:
//给定的字节数组创建缓冲区
Unsignedchar*buffer=(unsignedchar*)malloc(1024);
JobjectdirectBuffer;
directBuffer=(*env)->NewDirectByteBuffer(env,buffer,1024);
注:原生方法中分配的内存需要手动释放
直接字节缓冲区获取GetDirectBufferAddress
//通过java字节缓冲取原生字节数组
Unsignedchar*buffer;
Buffer=(unsignedchar*)(*env)->GetDirectBufferAddress(env,directBuffer);
3.4.4访问域
Java有两类域:实例域和静态域
@@@@@@@@@@获取域ID@@@@@@@@@@@
示例代码如下:
PublicclassJavaClass{
/*实例域*/
PrivateStringinstanceField=”instancefield”;
/*静态域*/
PrivatestaticStringstaticField=”staticfield”;
}
//用对象引用取得类
Jclassclazz;
Clazz=(*env)->GetObjectClass(env,instance);
//获取实例域的域ID
JfieldIDinstanceFieldID;
instanceFieldID=(*env)->GetFielID(env,clazz,”instanceField”,”Ljava/lang/String;”);
//获取静态域的域ID
jfieldIDstaticFielID;
staticFieldID=(*env)->GetStaticFieldID(env,clazz,”staticField”,”Ljava/lang/String;”);
@@@@@@@@@@@@@获取域@@@@@@@@@@@@@@@@
Get<type>Field
//获取实例域
JstringinstanceField;
instanceField=(*env)->GetObjectField(env,instance,instanceFieldID);
//获取动态域
JstringstaticField;
staticField=(*env)->GetStaticObjectField(env,clazz,staticFieldID);
3.4.5调用方法
Java有两类方法:实例方法和静态方法
示例代码:
PublicclassJavaClass{
/*实例方法*/
PrivateStringinstanceMethod(){
Return“instanceMethod”;
}
/*静态方法*/
PrivatestaticStringstaticMethod(){
Return“staticMethod”;
}
}
@@@@@@@@@@@@@@@@获取方法ID@@@@@@@@@@@@@@@@
//获取实例方法ID
jmethodIDinstanceMethodID;
instanceMethodID=(*env)->GetMethodID(env,clazz,”instanceMethod”,”()Ljava/lang/String;”);
//获取静态方法ID
jmethodIDstaticMethodID;
staticMethodID=(*env)->GetStaticMethodID(env,clazz,”staticMethod””()Ljava/lang/String;”);
@@@@@@@@@@@@@@@@调用方法@@@@@@@@@@@@@@@@@@
Call<type>Method
//调用实例方法
JstringinstanceMethodResult;
instanceMethodResult=(*env)->CallStringMethod(env,clazz,instanceMethodID);
//调用静态方法
JstringstaticMethodResult;
staticMethodResult=(*env)->CallStaticStringMethod(env,clazz,staticMethodID);
3.4.6域和方法描述符
Java类型签名映射
Java类型 | 签名 |
Boolean | Z |
Byte | B |
Char | C |
Short | S |
Int | I |
Long | J |
Float | F |
Double | D |
Fully-qualified-class | Lfully-qualified-class |
Type[] | [type |
Methodtype | (arg-type)ret-type |
Java类文件反汇编程序javap
@@@@@@@@@@@@@在命令行方式下运行@@@@@@@@@@@@@@@@@@@@
D:\workspace4\HelloJni>javap-classpathbin/classes-p-scom.example.hellojni.H
elloJni
Compiledfrom"HelloJni.java"
publicclasscom.example.hellojni.HelloJniextendsandroid.app.Activity{
static{};
Signature:()V
publiccom.example.hellojni.HelloJni();
Signature:()V
publicvoidonCreate(android.os.Bundle);
Signature:(Landroid/os/Bundle;)V
publicnativejava.lang.StringstringFromJNI();
Signature:()Ljava/lang/String;
publicnativejava.lang.StringunimplementedStringFromJNI();
Signature:()Ljava/lang/String;
}
@@@@@@@@@@@@@@@@在eclipse下运行@@@@@@@@@@@@@@@@@@@@@
Run-àexternaltool-àeternaltoolsconfigurations
选中programe,单击新建newlaunchconfiguration
选中main选项卡:
Name:JavaClassFileDisassembler
Location:${system_path:javap}
Workingdirectory:${project_loc}
Arguments:-classpath"${project_classpath};${env_var:ANDROID_SDK_HOME}/platforms/android-14/android.jar"–p–s${java_type_name}
选中common选项卡:选中displayinfavoritesmenu-àexternaltools
保存退出
测试
Compiledfrom"HelloJni.java"
publicclasscom.example.hellojni.HelloJniextendsandroid.app.Activity{
static{};
Signature:()V
publiccom.example.hellojni.HelloJni();
Signature:()V
publicvoidonCreate(android.os.Bundle);
Signature:(Landroid/os/Bundle;)V
publicnativejava.lang.StringstringFromJNI();
Signature:()Ljava/lang/String;
publicnativejava.lang.StringunimplementedStringFromJNI();
Signature:()Ljava/lang/String;
}
3.5异常处理
Jni中异常发生后要显示的实现异常处理
@@@@@@@@@@@@@捕获异常@@@@@@@@@@@@@@
//抛出异常的java例子
PublicclassJavaClass{
/*抛出方法*/
PrivatevoidthrowingMethod()throwsNullPointerException{
ThrownewNullPointerException(“nullpointer”);
}
/*声明原生方法*/
PrivatenativevoidaccessMethods();
}
//原生代码中的异常处理
Jthrowableex;
…
(*env)->CallVoidMethod(env,instance,throwingMethodId);
Ex=(*env)->ExceptionOccurred(env);
If(0!=ex){
(*env)->ExceptionClear(env);
/*exceptionhandler*/
}
@@@@@@@@@@@@@抛出异常@@@@@@@@@@@@@@@
//原生代码中抛出异常
Jclassclazz;
…
//找到异常类
Clazz=(*env)->FindClass(env,”java/lang/NullPointerException”);
If(0!=clazz){
//在抛出异常时应释放所有已分配的原生资源
(*env)->ThrowNew(env,clazz,”exceptionmessage!”);
}
3.6局部和全局引用
3.6.1局部引用
在原生函数反回后会自动释放,也可以用deleteLocalRef手动释放
示例代码:
//删除一个引用
Jclassclazz;
Clazz=(*env)->FindClass(env,”java/lang/string”);
…
(*env)->DeleteLocalRef(env,clazz);
ensureLocalCapacity请求局部引用槽
3.6.2全局引用
@@@@@@@@@@@@@@创建全局引用@@@@@@@@@@@@@@@@@
//用给定的局部引用创建全局引用
JclasslocalClazz;
JclassglobalClazz;
localClazz=(*env)->FindClass(env,”java/lang/String”);
globalClazz=(*env)->NewGlobalRef(env,localClazz);
…
(*env)->DeleteLocalRef(env,localClazz);
@@@@@@@@@@@@@@删除全局引用@@@@@@@@@@@@@@@@@
(*env)->DeleteGlobalRef(env,globalClazz);
3.6.3弱全局引用
@@@@@@@@@@@@@创建弱全局引用@@@@@@@@@@@@@@@@@@@
//用给定的局部引用创建弱全局引用
JclassweakGlobalClazz;
weakGlobalClazz=(*env)->NewGlobalRef(env,localClazz);
@@@@@@@@@@@@@@弱全局引用的有效性检验@@@@@@@@@@@@@@@@
//检验弱全局变量是否仍然有效
If(JNI_FALSE==(*env)->IsSameObject(env,weakGlobalClazz,NULL)){
/*对象仍处于活动状态可以使用*/
}else{
/*对象被垃圾回收器回收,不能使用*/
}
@@@@@@@@@@@@@@@删除弱全局引用@@@@@@@@@@@@@@@@@
//删除弱全局引用
(*env)->DeleteWeakGlobalRef(env,weakGlobalClazz);
3.7线程
局部引用不能在线程间共享,全局引用可以在线程间共享
@@@@@@@@@@@@@@同步@@@@@@@@@@@@@@
//java同步代码块
Synchronized(obj){
/*同步线程安全代码块*/
}
//java同步代码块的原生等价
If(JNI_OK==(*env)->MoniterEnter(env,obj)){
/*错误处理*/
}
/*同步线程安全代码块*/
If(JNI_OK==(*env)->MoniterExit(env,obj)){
/*错误处理*/
}
@@@@@@@@@@原生线程@@@@@@@@@@@@
//将当前线程和虚拟机附着和分离
JavaVM*cachedJvm;
JNIEnv*env;
..
/*将当前线程附着到虚拟机*/
(*cachedJvm)->AttachCurrentThread(cachedJvm,&env,NULL);
…
/*将当前线程与虚拟机分离*/
(*cachedJvm)->DetachCurrentThread(cachedJvm);