1 JNI注册:
C语言部分:
int register_android_Test(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "XXX/XXX/XXX", sMethod, NELEM(sMethod));
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
{
return result;
}
register_android_Test(env);
return JNI_VERSION_1_4;
}
Java部分代码
static
{
try
{
System.loadLibrary("XXX");
}
catch (UnsatisfiedLinkError ule)
{
Log.e("Test JNI", "WARNING: Could not load JNI :" + ule.toString());
}
}
在JAVA 中调用System.loadLibrary,系统会调用JNI库中定义的JNI_OnLoad,有JNI_OnLoad完成JNI函数的注册。
System.loadLibrary函数的参数是JNI库的文件名,但是不包括前面的lib和扩展名,例如我们的JNI库的文件libabcd.so,那么就要调用System.loadLibrary("abcd");
JNI库通常保存在目标机的system/lib下或者放在eclips工程下的libs\armeabi目录中。
jniRegisterNativeMethods完成JNI函数在JAVA中的映射,JNI函数是通过一个JNINativeMethod类型的数组来描述的。
jniRegisterNativeMethods函数的第二个参数是要映射的JAVA类的包名+类名。
例如定义了下面的类
package com.Android;
public class T1
{
.......
}
那么第二个参数就是"com/android/T1" ,这个名字不能搞错了,否者JAVA调用loadLibrary会报异常的。
2 JAVA类中访问JNI函数。
Andorid使用了JNINativeMethod描述了函数的参数和返回值,实现C到JAVA的映射
JNINativeMethod结构描述如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型和返回值一一对应的。
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
具体的每一个字符的对应关系如下
字符 Java类型 C类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
数组则以"["开始,用两个字符表示
字符 Java类型 C类型
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。
而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring
Ljava/lang/String; String 类
Ljava/net/Socket; Socket 类
如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。
例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
FileStatus就是在FileUtils类的内部定义的一个类
下面举个例子
在C代码中
static jint android_init(JNIEnv* env, jobject obj)
{
return 0;
}
static jboolean android_close(JNIEnv* env, jobject obj)
{
return true;
}
static jboolean android_Test(JNIEnv* env, jobject obj,jbyte para1,jbyte para2 , jbyte para3 )
{
return true;
}
static JNINativeMethod sMethod[] =
{
/* name, signature, funcPtr */
{"native_init", "()I", (void*)android_init},
{"native_close", "()Z", (void*)android_close},
{"native_Test", "(BBB)Z", (void*)android_Test},
};
int register_android_Test(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "com/android/test/mytest", sMethod, NELEM(sMethod));
}
register_android_Test函数在 JNI_OnLoad里面调用。
JAVA代码中
package com.android.test;
public class mytest
{
static {
try {
System.loadLibrary("test_jni"); //加载JNI库“libtest_jni.so”,这个库就是上面C代码编译出来的库
}
catch (UnsatisfiedLinkError ule) {
Log.e("JNI", "WARNING: Could not load libtest_jni.so :" + ule.toString());
}
}
public native int native_init( ); //注意JNI函数一定要加上native修饰
public native boolean native_close( );
public native boolean native_Test(byte group,byte bit , byte data );
}
在JAVA代码中要使用这些JNI函数,就直接new一个类调用就可以了,代码如下:
mytest myjniclass =new mytest;
myjniclass.native_init();
myjniclass.native_Test(1,2,3);
myjniclass.native_close();
3 JNI中回调JAVA函数
3.1 JNI中回调JAVA类的静态函数
例如 前面的JAVA类中定义了一个静态函数
public static void TestStaitc( )
{
}
在C代码中首先定义jmethodID和jclass类型的全局变量
static jclass gClazz;
static jmethodID method_TestStaitc;
在register_android_Test函数中增加下面的代码
jclass clazz = env->FindClass("com/android/test/mytest");
gClazz = jclass(env->NewGlobalRef(clazz));
method_TestStaitc = env->GetStaticMethodID(clazz,"TestStaitc","()V");
在回调的地方
env->CallStaticVoidMethod(gClazz,method_TestStaitc);
就大功告成了!
当然了上面对gClazz和method_TestStaitc初始化的代码也可以放在回调的地方调用,但是因为JAVA函数可能会多次调用,放在注册的时候调用可以减少系统的开销。
env->FindClass("com/android/test/mytest");返回的是一个局部引用,局部引用在本地方法返回时会自动释放,所以我们要调动gClazz = jclass(env->NewGlobalRef(clazz));创建一个全局引用,这个引用不会自动释放,我们在不需要这个引用的时候要显示调用env->DeleteGlobalRef(gClazz)来释放。
(注:除了FindClass,还有下面的函数也可以获取类的引用
jclass GetObjectClass(jobject obj) 根据一个对象,获取该对象的类
jclass GetSuperClass(jclass obj) 获取一个类的父类)
GetStaticMethodID 是返回静态方法的jmethodID,注意这个函数的第一参数一定是类的引用(jclass ),而不是对象的引用。第二个参数是JAVA中定义的函数名,第三个参数是JAVA函数的参数和返回值
同样道理CallStaticVoidMethod的第一个参数也必须是jclass类型,否者会报异常。
CallStaticVoidMethod 是调用无返回值的静态方法,JNI中还有许多其他返回类型的方法,如下:
CallStaticObjectMethod //返回一个JAVA的对象
CallStaticBooleanMethod
CallStaticByteMethod
CallStaticCharMethod
CallStaticShortMethod
CallStaticIntMethod
CallStaticLongMethod
CallStaticFloatMethod
CallStaticDoubleMethod
CallStaticVoidMethod
例如:如果JAVA函数原型为public static int TestStaitc( int para );
那么就调用
jint ret = env->CallStaticIntMethod(gClazz,method_TestStaitc,1);
这里注意一个参数一定要是类的引用,而不是对象的引用。
3.2 JNI中回调JAVA类的非静态函数
例如 前面的JAVA类中定义了一个静态函数
public void Test( )
{
}
在C代码中首先定义jmethodID 的全局变量
static jmethodID method_Test ;
在register_android_Test函数中增加下面的代码
jclass clazz = env->FindClass("com/android/test/mytest");
method_Test = env->GetMethodID(clazz,"Test","()V");
在回调的地方
env->CallVoidMethod(obj,method_Test);
就大功告成了!
这里要注意,obj的对象的引用(jobject),而不是类的引用(jclass);
jobject是通过JNI函数的第二个参数传递进来的,如果要创建一个全局的对象,可以调用gObject = env->NewGlobalRef(obj);
同样的除了CallVoidMethod ,JNI中还有许多其他返回类型的方法
CallObjectMethod //返回一个JAVA的对象
CallBooleanMethod
CallByteMethod
CallCharMethod
CallShortMethod
CallIntMethod
CallLongMethod
CallFloatMethod
CallDoubleMethod
CallVoidMethod
3.3 JNI调用Java方法不在一个类中。
前面介绍的都是JNI和JAVA在同一个类中,如果JNI和JAVA不在一个类中,如何访问呢?
例如我们要访问 adc/def包里面的test类的函数,的 void test(void) 函数 可以用下面的代码
jclass clazz = env->FindClass("adc/def/test");
jmethodID method1 = env->GetMethodID(clazz,"test","()V");
jobject obj= env->AllocObject(clazz);//这里创建一个对象实例,如果是静态函数,就不需要创建对象的示例了
env->CallVoidMethod(obj,method1); //调用函数,如果是静态函数,就应该是env->CallStaticVoidMethod(clazz,method1);前面的method1 = env->GetStaticMethodID(clazz,"test","()V");
AllocObject 仅仅是分配了一个test的对象,但是不会调用这个类的构造函数,那么对于需要构造函数的类该怎么处理呢?
首先需要获得构造函数的jmethodID
jmethodID methodInit = env->GetMethodID(clazz,"<init>","()V"); 注意这里的第二个参数不是构造函数的名字,而是<init>
然后创建对象
jobject objtest = env->NewObject( clazz,methodInit);
如果构造函数需要参数,就按参数的顺序依次加载methodInit的后面,就好了。
注意AllocObject和NewObject创建的都是局部引用,在方法结束后可能会被回收。如果要在其他的方法中使用,需要创建全局引用gObject = env->NewGlobalRef(objtest);
对于全局引用,在不需要再使用的时候,要显示的调用env->DeleteGlobalRef(gObject);
对于局部引用,可以不用显示的释放。也可以在函数结束时调用env->DeleteLocalRef(Object);释放
4 JNI中访问JAVA的变量
对于JNI中访问JAVA变量的方法和函数类似。
首先需要定义一个jfieldID。
jfieldID field;
JNI提供了两个函数 GetFieldID和GetStaticMethodID分别获取非静态变量和静态变量的jfieldID
然后用上面两个函数初始化的jfieldID变量。
获取或者设置这些变量的值可以用下面的函数
void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
void SetCharField(jobject obj, jfieldID fieldID, jchar value)
void SetShortField(jobject obj, jfieldID fieldID, jshort value)
void SetIntField(jobject obj, jfieldID fieldID, jint value)
void SetLongField(jobject obj, jfieldID fieldID, jlong value)
void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)
jobject GetObjectField(jobject obj, jfieldID fieldID)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jbyte GetByteField(jobject obj, jfieldID fieldID)
jchar GetCharField(jobject obj, jfieldID fieldID)
jshort GetShortField(jobject obj, jfieldID fieldID)
jint GetIntField(jobject obj, jfieldID fieldID)
jlong GetLongField(jobject obj, jfieldID fieldID)
jfloat GetFloatField(jobject obj, jfieldID fieldID)
jdouble GetDoubleField(jobject obj, jfieldID fieldID)
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
jint GetStaticIntField(jclass clazz, jfieldID fieldID)
jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)
下面举例说明如何使用,例如前面的mytest类里面定义了两个变量
int i;
String Message;
JNI C代码
定义两个全局变量
jfieldID mStringMessageId;
jfieldID nIntiId;
在register_android_Test函数中增加下面的代码
jclass clazz = env->FindClass("com/android/test/mytest");
nIntiId = env->GetFieldID(clazz, "i", "I");
mStringMessageId = env->GetFieldID(clazz, "Message", "Ljava/lang/String;");
获取变量的值
int i = GetIntField(obj,nIntiId);
jstring Messagestr = jstring(env->GetObjectField(obj, mStringMessageId));
设置变量
GetIntField(obj,nIntiId,5);
char tempbuf[10];
sprintf(tempbuf,"test" );
env->SetObjectField(obj, mStringMessageId, env->NewStringUTF(tempbuf));
对于访问JNI和JAVA不在同一个类的变量的方法,和3.3介绍的类似,这里就不详细描述了。
5 JNI参数的传递
5.1 JNI类型描述
Java类型 JNI本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 带符号的8位整型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
对于简单数据类型例如byte char int boolean等等只来的,直接使用就可以了
5.2数组的使用:
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数,XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
我们可以在C语言中操作这些指针了。操作完成功后ReleaseXXXArrayElements
函数 Java 数组类型 本地类型
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble
ReleaseBooleanArrayElements jbooleanArray jboolean
ReleaseByteArrayElements jbyteArray jbyte
ReleaseCharArrayElements jcharArray jchar
ReleaseShortArrayElements jshortArray jshort
ReleaseIntArrayElements jintArray jint
ReleaseLongArrayElements jlongArray jlong
ReleaseFloatArrayElements jfloatArray jfloat
ReleaseDoubleArrayElements jdoubleArray jdouble
具体可以看下面的例子
static jint Test(JNIEnv* env, jobject obj , jintArray buf )
{
jint* ptr= env->GetIntArrayElements(buf, 0 );
jsize len = env->GetArrayLength(buf);
int i;
for(i=0;i<len;i++)
{
ALOGD("buf[%d]=%d",i,ptr[i] );
ptr[i]=9;
}
env->ReleaseIntArrayElements(buf, ptr, 0 );
return 0;
}
执行完这个JNI函数后,JAVA传进来的数组的值就全部变为9了。
上面看到的是数组座位参数传递进来,那么如何返回一个数组呢?
我们可以调用NewXXXArray 创建一个数组,这个函数的参数就是要生成的数组的长度。然后return这个数组就可以了。如果需要给数组赋值,参考上面的代码GetIntArrayElements就可以了
下面是例子:
jintArray newbuf = env->NewIntArray(5);
ptr= env->GetIntArrayElements(newbuf, 0 );
for(i=0;i<5;i++)
{
ptr[i]=6;
}
env->ReleaseIntArrayElements(newbuf, ptr, 0 );
return newbuf;
对于其他数据类型的数组,调用相应的NewXXXArray就好了,把XXX换为相应的数据类型。
5.3对象的使用
通过使用之前介绍的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。
FindClass
GetMethodID
AllocObject
CallXXXMethod (XXX为相应的数据类型)
CallStaticXXXMethod(XXX为相应的数据类型)
GetStaticMethodID
NewObject
GetFieldID
GetStaticFieldID
GetXXXField(XXX为相应的数据类型)
SetXXXField(XXX为相应的数据类型)
5.4 String类的使用
其实String(jstring)也是数以一种对象类型,不过由于使用的频率非常高,所以JNI专门提供了一系列的函数进行操作。
在JAVA中String类都是16为的unicode,而C\C++中都是8位的char型,两者之间如何转换呢?
String到char型的转换:
const char *ptr = env->GetStringUTFChars(stringname, NULL);
jsize len = GetStringUTFLength(stringname) ;
这里就可以对ptr进行处理了,注意,ptr的内容不能改变。
处理完调用env->ReleaseStringUTFChars(stringname, ptr);
char型到String类型的转换
jstring value = NULL;
value = env->NewStringUTF(buf); //buf是char型的指针
6 JNI在线程中访问JAVA的函数和变量
JNIEnv 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ; 而线程和应用的主线程不在同一个线程中,那么其他的线程中如何访问JAVA的函数和类呢?
首先我们要在创建线程的JNI函数中初始化两个全局变量
static jobject gObject;
static JavaVM *gs_jvm;
gObject = env->NewGlobalRef(obj);
env->GetJavaVM(&gs_jvm);
在线程的开始处
JNIEnv *env1=NULL ;
gs_jvm->AttachCurrentThread(&env1, NULL);
之后就可以参考前面的方法访问JAVA的函数和变量了
在退出线程的地方调用
gs_jvm->DetachCurrentThread();
下面是示例代码:
static jobject gObject=NULL;
static JavaVM *gs_jvm=NULL;
static void * Test_thread( void* arg )
{
JNIEnv *env1=NULL ;
gs_jvm->AttachCurrentThread(&env1, NULL);
while(1)
{
//访问JAVA中的函数和变量
}
gs_jvm->DetachCurrentThread();
}
static jint StartInterrupt(JNIEnv* env, jobject obj )
{
pthread_t threadId;
if(gObject==NULL)
{
gObject = env->NewGlobalRef(obj);
}
if(gs_jvm==NULL)
{
env->GetJavaVM(&gs_jvm);
}
pthread_create( &threadId, NULL, Test_thread, NULL );
}
C语言部分:
int register_android_Test(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "XXX/XXX/XXX", sMethod, NELEM(sMethod));
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
{
return result;
}
register_android_Test(env);
return JNI_VERSION_1_4;
}
Java部分代码
static
{
try
{
System.loadLibrary("XXX");
}
catch (UnsatisfiedLinkError ule)
{
Log.e("Test JNI", "WARNING: Could not load JNI :" + ule.toString());
}
}
在JAVA 中调用System.loadLibrary,系统会调用JNI库中定义的JNI_OnLoad,有JNI_OnLoad完成JNI函数的注册。
System.loadLibrary函数的参数是JNI库的文件名,但是不包括前面的lib和扩展名,例如我们的JNI库的文件libabcd.so,那么就要调用System.loadLibrary("abcd");
JNI库通常保存在目标机的system/lib下或者放在eclips工程下的libs\armeabi目录中。
jniRegisterNativeMethods完成JNI函数在JAVA中的映射,JNI函数是通过一个JNINativeMethod类型的数组来描述的。
jniRegisterNativeMethods函数的第二个参数是要映射的JAVA类的包名+类名。
例如定义了下面的类
package com.Android;
public class T1
{
.......
}
那么第二个参数就是"com/android/T1" ,这个名字不能搞错了,否者JAVA调用loadLibrary会报异常的。
2 JAVA类中访问JNI函数。
Andorid使用了JNINativeMethod描述了函数的参数和返回值,实现C到JAVA的映射
JNINativeMethod结构描述如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型和返回值一一对应的。
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
具体的每一个字符的对应关系如下
字符 Java类型 C类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
数组则以"["开始,用两个字符表示
字符 Java类型 C类型
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。
而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring
Ljava/lang/String; String 类
Ljava/net/Socket; Socket 类
如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。
例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
FileStatus就是在FileUtils类的内部定义的一个类
下面举个例子
在C代码中
static jint android_init(JNIEnv* env, jobject obj)
{
return 0;
}
static jboolean android_close(JNIEnv* env, jobject obj)
{
return true;
}
static jboolean android_Test(JNIEnv* env, jobject obj,jbyte para1,jbyte para2 , jbyte para3 )
{
return true;
}
static JNINativeMethod sMethod[] =
{
/* name, signature, funcPtr */
{"native_init", "()I", (void*)android_init},
{"native_close", "()Z", (void*)android_close},
{"native_Test", "(BBB)Z", (void*)android_Test},
};
int register_android_Test(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "com/android/test/mytest", sMethod, NELEM(sMethod));
}
register_android_Test函数在 JNI_OnLoad里面调用。
JAVA代码中
package com.android.test;
public class mytest
{
static {
try {
System.loadLibrary("test_jni"); //加载JNI库“libtest_jni.so”,这个库就是上面C代码编译出来的库
}
catch (UnsatisfiedLinkError ule) {
Log.e("JNI", "WARNING: Could not load libtest_jni.so :" + ule.toString());
}
}
public native int native_init( ); //注意JNI函数一定要加上native修饰
public native boolean native_close( );
public native boolean native_Test(byte group,byte bit , byte data );
}
在JAVA代码中要使用这些JNI函数,就直接new一个类调用就可以了,代码如下:
mytest myjniclass =new mytest;
myjniclass.native_init();
myjniclass.native_Test(1,2,3);
myjniclass.native_close();
3 JNI中回调JAVA函数
3.1 JNI中回调JAVA类的静态函数
例如 前面的JAVA类中定义了一个静态函数
public static void TestStaitc( )
{
}
在C代码中首先定义jmethodID和jclass类型的全局变量
static jclass gClazz;
static jmethodID method_TestStaitc;
在register_android_Test函数中增加下面的代码
jclass clazz = env->FindClass("com/android/test/mytest");
gClazz = jclass(env->NewGlobalRef(clazz));
method_TestStaitc = env->GetStaticMethodID(clazz,"TestStaitc","()V");
在回调的地方
env->CallStaticVoidMethod(gClazz,method_TestStaitc);
就大功告成了!
当然了上面对gClazz和method_TestStaitc初始化的代码也可以放在回调的地方调用,但是因为JAVA函数可能会多次调用,放在注册的时候调用可以减少系统的开销。
env->FindClass("com/android/test/mytest");返回的是一个局部引用,局部引用在本地方法返回时会自动释放,所以我们要调动gClazz = jclass(env->NewGlobalRef(clazz));创建一个全局引用,这个引用不会自动释放,我们在不需要这个引用的时候要显示调用env->DeleteGlobalRef(gClazz)来释放。
(注:除了FindClass,还有下面的函数也可以获取类的引用
jclass GetObjectClass(jobject obj) 根据一个对象,获取该对象的类
jclass GetSuperClass(jclass obj) 获取一个类的父类)
GetStaticMethodID 是返回静态方法的jmethodID,注意这个函数的第一参数一定是类的引用(jclass ),而不是对象的引用。第二个参数是JAVA中定义的函数名,第三个参数是JAVA函数的参数和返回值
同样道理CallStaticVoidMethod的第一个参数也必须是jclass类型,否者会报异常。
CallStaticVoidMethod 是调用无返回值的静态方法,JNI中还有许多其他返回类型的方法,如下:
CallStaticObjectMethod //返回一个JAVA的对象
CallStaticBooleanMethod
CallStaticByteMethod
CallStaticCharMethod
CallStaticShortMethod
CallStaticIntMethod
CallStaticLongMethod
CallStaticFloatMethod
CallStaticDoubleMethod
CallStaticVoidMethod
例如:如果JAVA函数原型为public static int TestStaitc( int para );
那么就调用
jint ret = env->CallStaticIntMethod(gClazz,method_TestStaitc,1);
这里注意一个参数一定要是类的引用,而不是对象的引用。
3.2 JNI中回调JAVA类的非静态函数
例如 前面的JAVA类中定义了一个静态函数
public void Test( )
{
}
在C代码中首先定义jmethodID 的全局变量
static jmethodID method_Test ;
在register_android_Test函数中增加下面的代码
jclass clazz = env->FindClass("com/android/test/mytest");
method_Test = env->GetMethodID(clazz,"Test","()V");
在回调的地方
env->CallVoidMethod(obj,method_Test);
就大功告成了!
这里要注意,obj的对象的引用(jobject),而不是类的引用(jclass);
jobject是通过JNI函数的第二个参数传递进来的,如果要创建一个全局的对象,可以调用gObject = env->NewGlobalRef(obj);
同样的除了CallVoidMethod ,JNI中还有许多其他返回类型的方法
CallObjectMethod //返回一个JAVA的对象
CallBooleanMethod
CallByteMethod
CallCharMethod
CallShortMethod
CallIntMethod
CallLongMethod
CallFloatMethod
CallDoubleMethod
CallVoidMethod
3.3 JNI调用Java方法不在一个类中。
前面介绍的都是JNI和JAVA在同一个类中,如果JNI和JAVA不在一个类中,如何访问呢?
例如我们要访问 adc/def包里面的test类的函数,的 void test(void) 函数 可以用下面的代码
jclass clazz = env->FindClass("adc/def/test");
jmethodID method1 = env->GetMethodID(clazz,"test","()V");
jobject obj= env->AllocObject(clazz);//这里创建一个对象实例,如果是静态函数,就不需要创建对象的示例了
env->CallVoidMethod(obj,method1); //调用函数,如果是静态函数,就应该是env->CallStaticVoidMethod(clazz,method1);前面的method1 = env->GetStaticMethodID(clazz,"test","()V");
AllocObject 仅仅是分配了一个test的对象,但是不会调用这个类的构造函数,那么对于需要构造函数的类该怎么处理呢?
首先需要获得构造函数的jmethodID
jmethodID methodInit = env->GetMethodID(clazz,"<init>","()V"); 注意这里的第二个参数不是构造函数的名字,而是<init>
然后创建对象
jobject objtest = env->NewObject( clazz,methodInit);
如果构造函数需要参数,就按参数的顺序依次加载methodInit的后面,就好了。
注意AllocObject和NewObject创建的都是局部引用,在方法结束后可能会被回收。如果要在其他的方法中使用,需要创建全局引用gObject = env->NewGlobalRef(objtest);
对于全局引用,在不需要再使用的时候,要显示的调用env->DeleteGlobalRef(gObject);
对于局部引用,可以不用显示的释放。也可以在函数结束时调用env->DeleteLocalRef(Object);释放
4 JNI中访问JAVA的变量
对于JNI中访问JAVA变量的方法和函数类似。
首先需要定义一个jfieldID。
jfieldID field;
JNI提供了两个函数 GetFieldID和GetStaticMethodID分别获取非静态变量和静态变量的jfieldID
然后用上面两个函数初始化的jfieldID变量。
获取或者设置这些变量的值可以用下面的函数
void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
void SetCharField(jobject obj, jfieldID fieldID, jchar value)
void SetShortField(jobject obj, jfieldID fieldID, jshort value)
void SetIntField(jobject obj, jfieldID fieldID, jint value)
void SetLongField(jobject obj, jfieldID fieldID, jlong value)
void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)
jobject GetObjectField(jobject obj, jfieldID fieldID)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jbyte GetByteField(jobject obj, jfieldID fieldID)
jchar GetCharField(jobject obj, jfieldID fieldID)
jshort GetShortField(jobject obj, jfieldID fieldID)
jint GetIntField(jobject obj, jfieldID fieldID)
jlong GetLongField(jobject obj, jfieldID fieldID)
jfloat GetFloatField(jobject obj, jfieldID fieldID)
jdouble GetDoubleField(jobject obj, jfieldID fieldID)
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
jint GetStaticIntField(jclass clazz, jfieldID fieldID)
jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)
下面举例说明如何使用,例如前面的mytest类里面定义了两个变量
int i;
String Message;
JNI C代码
定义两个全局变量
jfieldID mStringMessageId;
jfieldID nIntiId;
在register_android_Test函数中增加下面的代码
jclass clazz = env->FindClass("com/android/test/mytest");
nIntiId = env->GetFieldID(clazz, "i", "I");
mStringMessageId = env->GetFieldID(clazz, "Message", "Ljava/lang/String;");
获取变量的值
int i = GetIntField(obj,nIntiId);
jstring Messagestr = jstring(env->GetObjectField(obj, mStringMessageId));
设置变量
GetIntField(obj,nIntiId,5);
char tempbuf[10];
sprintf(tempbuf,"test" );
env->SetObjectField(obj, mStringMessageId, env->NewStringUTF(tempbuf));
对于访问JNI和JAVA不在同一个类的变量的方法,和3.3介绍的类似,这里就不详细描述了。
5 JNI参数的传递
5.1 JNI类型描述
Java类型 JNI本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 带符号的8位整型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
对于简单数据类型例如byte char int boolean等等只来的,直接使用就可以了
5.2数组的使用:
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数,XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
我们可以在C语言中操作这些指针了。操作完成功后ReleaseXXXArrayElements
函数 Java 数组类型 本地类型
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble
ReleaseBooleanArrayElements jbooleanArray jboolean
ReleaseByteArrayElements jbyteArray jbyte
ReleaseCharArrayElements jcharArray jchar
ReleaseShortArrayElements jshortArray jshort
ReleaseIntArrayElements jintArray jint
ReleaseLongArrayElements jlongArray jlong
ReleaseFloatArrayElements jfloatArray jfloat
ReleaseDoubleArrayElements jdoubleArray jdouble
具体可以看下面的例子
static jint Test(JNIEnv* env, jobject obj , jintArray buf )
{
jint* ptr= env->GetIntArrayElements(buf, 0 );
jsize len = env->GetArrayLength(buf);
int i;
for(i=0;i<len;i++)
{
ALOGD("buf[%d]=%d",i,ptr[i] );
ptr[i]=9;
}
env->ReleaseIntArrayElements(buf, ptr, 0 );
return 0;
}
执行完这个JNI函数后,JAVA传进来的数组的值就全部变为9了。
上面看到的是数组座位参数传递进来,那么如何返回一个数组呢?
我们可以调用NewXXXArray 创建一个数组,这个函数的参数就是要生成的数组的长度。然后return这个数组就可以了。如果需要给数组赋值,参考上面的代码GetIntArrayElements就可以了
下面是例子:
jintArray newbuf = env->NewIntArray(5);
ptr= env->GetIntArrayElements(newbuf, 0 );
for(i=0;i<5;i++)
{
ptr[i]=6;
}
env->ReleaseIntArrayElements(newbuf, ptr, 0 );
return newbuf;
对于其他数据类型的数组,调用相应的NewXXXArray就好了,把XXX换为相应的数据类型。
5.3对象的使用
通过使用之前介绍的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。
FindClass
GetMethodID
AllocObject
CallXXXMethod (XXX为相应的数据类型)
CallStaticXXXMethod(XXX为相应的数据类型)
GetStaticMethodID
NewObject
GetFieldID
GetStaticFieldID
GetXXXField(XXX为相应的数据类型)
SetXXXField(XXX为相应的数据类型)
5.4 String类的使用
其实String(jstring)也是数以一种对象类型,不过由于使用的频率非常高,所以JNI专门提供了一系列的函数进行操作。
在JAVA中String类都是16为的unicode,而C\C++中都是8位的char型,两者之间如何转换呢?
String到char型的转换:
const char *ptr = env->GetStringUTFChars(stringname, NULL);
jsize len = GetStringUTFLength(stringname) ;
这里就可以对ptr进行处理了,注意,ptr的内容不能改变。
处理完调用env->ReleaseStringUTFChars(stringname, ptr);
char型到String类型的转换
jstring value = NULL;
value = env->NewStringUTF(buf); //buf是char型的指针
6 JNI在线程中访问JAVA的函数和变量
JNIEnv 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ; 而线程和应用的主线程不在同一个线程中,那么其他的线程中如何访问JAVA的函数和类呢?
首先我们要在创建线程的JNI函数中初始化两个全局变量
static jobject gObject;
static JavaVM *gs_jvm;
gObject = env->NewGlobalRef(obj);
env->GetJavaVM(&gs_jvm);
在线程的开始处
JNIEnv *env1=NULL ;
gs_jvm->AttachCurrentThread(&env1, NULL);
之后就可以参考前面的方法访问JAVA的函数和变量了
在退出线程的地方调用
gs_jvm->DetachCurrentThread();
下面是示例代码:
static jobject gObject=NULL;
static JavaVM *gs_jvm=NULL;
static void * Test_thread( void* arg )
{
JNIEnv *env1=NULL ;
gs_jvm->AttachCurrentThread(&env1, NULL);
while(1)
{
//访问JAVA中的函数和变量
}
gs_jvm->DetachCurrentThread();
}
static jint StartInterrupt(JNIEnv* env, jobject obj )
{
pthread_t threadId;
if(gObject==NULL)
{
gObject = env->NewGlobalRef(obj);
}
if(gs_jvm==NULL)
{
env->GetJavaVM(&gs_jvm);
}
pthread_create( &threadId, NULL, Test_thread, NULL );
}