第一章 用JNI实现与原生代码通信
3.1 什么是jni
3.2 一个简单的示例
示例代码中查看实现步骤:
@@@@@@@@@@A 加载共享库@@@@@@@@@@@
static {
System.loadLibrary("hello-jni");
}
@@@@@@@@@@B 声明原生方法 @@@@@@@@@@
public native String stringFromJNI();
public native String unimplementedStringFromJNI();
@@@@@@@@@@@@@@@C 调用原生方法@@@@@@@@@@@@@@@@@@@
tv.setText( stringFromJNI() );
hello-jni.c中实现原生方法
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jobject thiz )
{
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
c/c++ 头文件生成器javah
D:\workspace4\HelloJni>javah -classpath bin/classes -bootclasspath d:\tools\andr
oid-sdk-windows\platforms\android-10\android.jar -d jni com.example.hellojni.Hel
loJni
命令格介绍如下
D:\workspace4\HelloJni\jni>javah
用法:
javah [options]
其中, [options] 包括:
-o
-d
-v -verbose 启用详细输出
-h --help -? 输出此消息
-version 输出版本信息
-jni 生成 JNI 样式的标头文件 (默认值)
-force 始终写入输出文件
-classpath
-bootclasspath
(例如, java.lang.Object)。
在jni目录下生成头文件com_example_hellojni_HelloJni.h,内容如下:
#include
/* Header for class com_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;
*/
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
(JNIEnv *, jobject);
/*
* Class: com_example_hellojni_HelloJni
* Method: unimplementedStringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
@@@@@@@@@@可以在eclipse中生成头文件@@@@@@@@@@@@@
Run-àexternal tool -àeternal tools configurations
选中programe,单击新建new launch configuration
选中main选项卡:
Name:Generate C and C++ Header File
Location: ${system_path:javah}
Working directory:${project_loc}/jni
Arguments:-classpath "${project_classpath};${env_var:ANDROID_SDK_HOME}/platforms/android-14/android.jar"${java_type_name}
选中refresh选项卡:选中refresh resources upon completionà
the project containing the selected resource
选中common选项卡:选中display in favorites menu-àexternal tools
保存退出
测试生成头文件:
选中测试目标类文件-àrun-à external tool -à Generate C and C++ Header File
会在jni目录下生成头文件
方法声明
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(
JNIEnv *, //指向jni函数表的接口指针
jobject); //hellojni类的java对象引用
JNIEnv接口指针
原生代码通过jnienv接口指针提供的各种函数来使用虚拟机的功能
return (*env)->NewStringUTF(env, "Hello from JNI !");
实例方法和静态方法
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jobject thiz )
实例方法可以通过第二个参数thiz取得(jobject实例引用)
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jclass clazz )
静态方法由于没有和实例绑定,通过第二个参数取得类引用jclass
3.3 数据类型
Java基本数据类型
Java类型 |
Jni类型 |
c/c++类型 |
大小 |
Boolean |
Jbollean |
Unsigned char |
无符号8位 |
Byte |
Jbyte |
Char |
有符号8位 |
Char |
Jchar |
Unsigned short |
无符号16位 |
Short |
Jshort |
Short |
有符号16位 |
Int |
Jint |
Int |
有符号32位 |
Long |
Jlong |
Long long |
有符号64位 |
Float |
Jfloat |
Float |
32位 |
double |
jdouble |
double |
64位 |
Java引用类型映射
Java类型 |
原生类型 |
Java.lang.class |
Jclass |
Java.lang.throwable |
Jthrowable |
Java.lang.string |
Jstring |
Other objects |
Jobjects |
Java.lang.object[] |
JobjectsArray |
Boolean[] |
jbooleanArray |
Byte[] |
jbyteArray |
Char[] |
jcharArray |
Short[] |
jshortArray |
Int[] |
jintArray |
Long[] |
jlongArray |
Float[] |
jfloatArray |
Double[] |
jdoubleArray |
Other Arrays |
jarray |
3.4 对引用数据类型的操作
3.4.1对字符串操作
创建字符串
示例代码如下:
Jstring javaString;
javaString=(*env) ->NewStringUTF(env,“hello world!”);
把java字符串转换为c字符串
GetStringChars 将unicode格式的java字符串转换为c 字符串
GetStringUTFChars 将utf-8格式的字符串转换为c字符串
注:以上两个函数的三个参数用来确定返回字串是指向副本还是栈中固定对象
示例代码如下:
Const jbyte* str;
Jboolean isCopy;
Str=(*env)->GetStringUTFChar(env,jstring,&isCopy);
If(0!=str){
Printf(“java string is : %s”,str);
If(JNI_TRUE==isCopy){
Printf(“c string is copy of the java string!”);
}else{
Printf(“c string points to actual string!”);
}
}
释放字符串
ReleaseStringChar用来释放unicode格式的字符串
ReleaseStringUTFChar来释放utf-8格式的字符串
示例代码如下:
(*env)->ReleaseStringChar(env,javaString,sstr);
3.4.2 数组操作
创建数组 New
示例代码如下:
jintArraay javaArray;
javaArray=(*env)->NewIntArray(ent,10);
if(0!=javaArray){
//数组可以使用
}
访问数组
将数组复制成c数组
让jni提供指向数组的指针
对副本的操作
Get
Set
//将java数组复制到c数组中
Jint nativeArray[10];
(*env)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);
//从c数组向java数组提交所作 的修改
(*env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);
对直接指针的操作
Get
Release
//取得指向java数组元素的直接指针
jint* nativeDirectArray;
jboolean isCopy;
nativeDirectArray=(*env)->GetIntArrayElements(env,javaArray,&isCopy);
//释放指向java数组的直接指针
(*env)->ReleaseIntArrayElements(env,javaArray,nativeDirectArray,0);
注:第四个参数为释放模式
0 将内容复制回来并释放原生数组
JNI_COMMIT 将内容复制回来但不释放原生数组,一般用于更新数组
JNI_ABORT 不将内容复制回来,释放原生数组
3.4.3 NIO操作
直接创建字节缓冲 NewDirectByteBuffer
示例代码如下:
//给定的字节数组创建缓冲区
Unsigned char* buffer=(unsigned char*) malloc(1024);
Jobject directBuffer;
directBuffer=(*env)->NewDirectByteBuffer(env,buffer,1024);
注:原生方法中分配的内存需要手动释放
直接字节缓冲区获取 GetDirectBufferAddress
//通过java字节缓冲取原生字节数组
Unsigned char* buffer;
Buffer=(unsigned char*)(*env)->GetDirectBufferAddress(env,directBuffer);
3.4.4 访问域
Java有两类域 :实例域和静态域
@@@@@@@@@@获取域ID@@@@@@@@@@@
示例代码如下:
Public class JavaClass{
/*实例域*/
Private String instanceField=”instance field”;
/*静态域*/
Private static String staticField=”static field”;
}
//用对象引用取得类
Jclass clazz;
Clazz=(*env)->GetObjectClass(env,instance);
//获取实例域的域ID
JfieldID instanceFieldID;
instanceFieldID=(*env)->GetFielID(env,clazz,”instanceField”,”Ljava/lang/String;”);
//获取静态域的域ID
jfieldID staticFielID;
staticFieldID=(*env)->GetStaticFieldID(env,clazz,”staticField”,”Ljava/lang/String;”);
@@@@@@@@@@@@@获取域@@@@@@@@@@@@@@@@
Get
//获取实例域
Jstring instanceField;
instanceField=(*env)->GetObjectField(env,instance,instanceFieldID);
//获取动态域
Jstring staticField;
staticField=(*env)->GetStaticObjectField(env,clazz,staticFieldID);
3.4.5 调用方法
Java有两类方法:实例方法和静态方法
示例代码:
Public class JavaClass{
/*实例方法*/
Private String instanceMethod(){
Return “instance Method”;
}
/*静态方法*/
Private static String staticMethod(){
Return “static Method”;
}
}
@@@@@@@@@@@@@@@@获取方法ID@@@@@@@@@@@@@@@@
//获取实例方法ID
jmethodID instanceMethodID;
instanceMethodID=(*env)->GetMethodID(env,clazz,”instanceMethod”,”()Ljava/lang/String;”);
//获取静态方法ID
jmethodID staticMethodID;
staticMethodID=(*env)->GetStaticMethodID(env,clazz,”staticMethod””()Ljava/lang/String;”);
@@@@@@@@@@@@@@@@调用方法@@@@@@@@@@@@@@@@@@
Call
//调用实例方法
Jstring instanceMethodResult;
instanceMethodResult=(*env)->CallStringMethod(env,clazz,instanceMethodID);
//调用静态方法
Jstring staticMethodResult;
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 |
Method type |
(arg-type)ret-type |
Java类文件反汇编程序 javap
@@@@@@@@@@@@@在命令行方式下运行@@@@@@@@@@@@@@@@@@@@
D:\workspace4\HelloJni>javap -classpath bin/classes -p -s com.example.hellojni.H
elloJni
Compiled from "HelloJni.java"
public class com.example.hellojni.HelloJni extends android.app.Activity {
static {};
Signature: ()V
public com.example.hellojni.HelloJni();
Signature: ()V
public void onCreate(android.os.Bundle);
Signature: (Landroid/os/Bundle;)V
public native java.lang.String stringFromJNI();
Signature: ()Ljava/lang/String;
public native java.lang.String unimplementedStringFromJNI();
Signature: ()Ljava/lang/String;
}
@@@@@@@@@@@@@@@@在eclipse下运行@@@@@@@@@@@@@@@@@@@@@
Run-àexternal tool -àeternal tools configurations
选中programe,单击新建new launch configuration
选中main选项卡:
Name: Java Class File Disassembler
Location: ${system_path:javap}
Working directory:${project_loc}
Arguments:-classpath "${project_classpath};${env_var:ANDROID_SDK_HOME}/platforms/android-14/android.jar" –p –s ${java_type_name}
选中common选项卡:选中display in favorites menu-àexternal tools
保存退出
测试
Compiled from "HelloJni.java"
public class com.example.hellojni.HelloJni extends android.app.Activity {
static {};
Signature: ()V
public com.example.hellojni.HelloJni();
Signature: ()V
public void onCreate(android.os.Bundle);
Signature: (Landroid/os/Bundle;)V
public native java.lang.String stringFromJNI();
Signature: ()Ljava/lang/String;
public native java.lang.String unimplementedStringFromJNI();
Signature: ()Ljava/lang/String;
}
3.5 异常处理
Jni中异常发生后要显示的实现异常处理
@@@@@@@@@@@@@捕获异常@@@@@@@@@@@@@@
//抛出异常的java例子
Public class JavaClass{
/*抛出方法*/
Private void throwingMethod() throws NullPointerException{
Throw new NullPointerException(“null pointer”);
}
/*声明原生方法*/
Private native void accessMethods();
}
//原生代码中的异常处理
Jthrowable ex;
…
(*env)->CallVoidMethod(env,instance,throwingMethodId);
Ex=(*env)->ExceptionOccurred(env);
If(0!=ex){
(*env)->ExceptionClear(env);
/*exception handler*/
}
@@@@@@@@@@@@@抛出异常@@@@@@@@@@@@@@@
//原生代码中抛出异常
Jclass clazz;
…
//找到异常类
Clazz=(*env)->FindClass(env,”java/lang/NullPointerException”);
If(0!=clazz){
//在抛出异常时应释放所有已分配的原生资源
(*env)->ThrowNew(env,clazz,”exception message!”);
}
3.6 局部和全局引用
3.6.1 局部引用
在原生函数反回后会自动释放,也可以用deleteLocalRef手动释放
示例代码:
//删除一个引用
Jclass clazz;
Clazz=(*env)->FindClass(env,”java/lang/string”);
…
(*env)->DeleteLocalRef(env,clazz);
ensureLocalCapacity请求局部引用槽
3.6.2 全局引用
@@@@@@@@@@@@@@创建全局引用@@@@@@@@@@@@@@@@@
//用给定的局部引用创建全局引用
Jclass localClazz;
Jclass globalClazz;
localClazz=(*env)->FindClass(env,”java/lang/String”);
globalClazz=(*env)->NewGlobalRef(env,localClazz);
…
(*env)->DeleteLocalRef(env,localClazz);
@@@@@@@@@@@@@@删除全局引用@@@@@@@@@@@@@@@@@
(*env)->DeleteGlobalRef(env,globalClazz);
3.6.3 弱全局引用
@@@@@@@@@@@@@创建弱全局引用@@@@@@@@@@@@@@@@@@@
//用给定的局部引用创建弱全局引用
Jclass weakGlobalClazz;
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);