第一章用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);