您的位置:首页 > 移动开发 > Android开发

Android: JNI本地函数控制Java端代码

2015-12-12 15:03 423 查看
C语言编写的JNI本地函数控制Java端的代码,主要包括以下内容:

1创建Java对象
2访问类静态成员域
3调用类的静态方法
4访问Java对象的成员变量
5访问Java对象的方法


示例程序逻辑图



整个程序冲JniFuncMain类中的createJniObject()本地方法开始,经由JNI与libjnifunc.so中命名为Java_JniFuncMain_creeateJniObject()的C函数链接在一起。然后在该C函数里面对JniTest类对象的创建,方法的调用等

下面贴出JniFuncMain.java的代码

public class JniFuncMain
{
private static int staticIntField = 300;
//加载本地库libjnifunc.so
static
{
System.loadLibrary("jnifunc");
}
//本地方法声明
public static native JniTest createJniObject();
public static void main(String[] args)
{
System.out.println("[Java] createJniObeject()" );
//从本地代码中生成JniTest对象
JniTest jniObj = createJniObject();
//调用JniTest对象的方法
jniObj.callTest();
}
}
class JniTest
{
private int intField;

//构造方法
public  JniTest(int num)
{
intField=num;
System.out.println("[Java] 调用JniTest对象的构造方法:intField = " + intField);
}

//此方法由JNI本地函数调用
public int callByNative(int num)
{
System.out.println("[Java]JNiTest对象的callByNative("+num+")调用");
return num;
}

//此方法由JniFuncMain类调用
public void callTest()
{
System.out.println("[Java]JniTest对象的callTest()方法调用:intField="+intField);

}
}


代码JniFuncMain类中的本地方法声明与之前相比多了一个static关键字,声明createJniObject(),static的作用是,在调用此方法时,不需要用new创建对象在其下面的main方法中有验证,

然后执行javac JniFuncMain.java生成class文件

javah JniFuncMain 生成.h文件,

细心一点会发现这里的JniFuncMain.h与之前的有些许不同

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JniFuncMain */

#ifndef _Included_JniFuncMain
#define _Included_JniFuncMain
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     JniFuncMain
* Method:    createJniObject
* Signature: ()LJniTest;
*/
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
(JNIEnv *, jclass);//第二个参数类型不同

#ifdef __cplusplus
}
#endif
#endif


Android :JNI基本原理

中.h的第二个参数类型是jobject,此处的是jclass,原因就是在类中声明本地方法时候的static关键字,在Java中,调用静态方法时,可以不同创建对象,通过所在的类直接调用即可,即本地静态方法通过类而非对象进行调用,因此函数原型的第二个参数类型为jclass类型,也就是说Java_JniFuncMain_createJniObject()的第二个参数保存的是JniFuncMain类的引用。

jnifunc.cpp文件

代码虽然简单,但却能准确的说明JNI函数如何使用Java组成元素

#include "JniFuncMain.h"
#include <stdio.h>
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env, jclass clazz)
{
jclass targetClass;
jmethodID mid;
jobject newObject;
jstring helloStr;
jfieldID fid;
jint staticIntField;
jint result;

//获取JniFuncMain类的staticIntField变量值
fid = env->GetStaticFieldID(clazz,"staticIntField","I");
staticIntField = env->GetStaticIntField(clazz,fid);
printf ("[CPP]JniFuncMainstaticIntField");
printf("    JniFunMain.staticIntField = %d\n",staticIntField);

//查找生成对象的类

targetClass = env->FindClass("JniTest");

//查看构造方法
mid = env->GetMethodID(targetClass,"<init>","(I)V");

//生成JniTest对象(返回对象的引用)
printf("[CPP]JniTest\n");
newObject = env->NewObject(targetClass,mid,100);
//获取调用对象的方法ID
mid = env->GetMethodID(targetClass,"callByNative","(I)I");
//调用Java方法 保存返回值
result = env->CallIntMethod(newObject,mid,200);

//设置newObject对象的intField

fid = env->GetFieldID(targetClass,"intField","I");
printf("[CPP]JniTestintField200\n");
env->SetIntField(newObject,fid,result);

//返回对象的引用
return newObject;
}


逐步分析

1通过JNI获取成员变量值

//查找含有待访问变量的JniFuncMain类的jclass值
//获取JniFuncMain类的staticIntField变量值
fid = env->GetStaticFieldID(clazz,"staticIntField","I");//(1)
staticIntField = env->GetStaticIntField(clazz,fid);//(2)


(1)获取此类成员变量的jfieldID值,若成员变量为静态变量,则调用名称为GetStaticFieldID()的JNI函数,若待访问的成员变量是普通对象,则调用名称为GetFieldID()的JNI函数。

(2)用函数参数中的jclass与上步获得的jfieldID值,获取或设置成员变量值。

若想在本地代码中访问Java的成员变量,必须获取相应成员变量的ID值。成员变量的ID保存在jfieldID类型的变量中。

jfield GetStaticFieldID(JNIEnv * env, jclass clazz, const char * name, const char * signature);


说明 返回指定类型的静态成员变量的jfield的值

参数 env-JNI接口指针

clazz-包含成员变量的类的jclass

name-成员变量名

signature-成员变量签名

与之对应的还有 GetFieldID() 不过是返回普通成员变量的值

< jnitype > GetStatic< type >Field(JNIEnv * env,jclass clazz,jfieldID fieldID)


说明 返回clazz类中ID为fieldID的静态变量的值

参数 env-JNI接口指针

clazz-包含成员变量的类

fieldID-成员变量的ID

参考 < type > 指Object、Boolean、Byte、Char、Short、Int、Long、Float、Double九种基本类型

返回类型 < jnitype >指jobject、jboolean、jbyte、jchar、jshort、jint、jlong、jfloat、jdouble九种基本类型

与之对应的是

< jnitype > Get< type >Field(JNIEnv * env,jobject obj,jfieldID fieldID)

其返回的是obj对象中ID为fieldID的成员变量的值 fieldID是成员变量的ID

稍微细心一点会发现我们例子中的方法与上面方法说明中参数差一个JNIEnv * env 具体请详见

Android:JNI函数编码风格(C风格与C++风格)

查看签名的方法

javap -p -s 类名

-S 输出Java签名

-p输出所有类及成员



2通过JNI本地函数生成Java对象

//查找生成对象的类

targetClass = env->FindClass("JniTest");//(1)

//查看构造方法
mid = env->GetMethodID(targetClass,"<init>","(I)V");//(2)

//生成JniTest对象(返回对象的引用)
printf("[CPP]JniTest\n");
newObject = env->NewObject(targetClass,mid,100);//(3)


(1)查找指定的类,并将查找到的类赋给jclass类型的变量

(2)查找Java类构造方法的ID值,类型为jmethodID。

(3)生成Java类对象

首先调用JNI函数FindClass(),查找生成对象的类

jclass FindClass(JNIEnv * env,const char * name)


说明 查找name指定的Java类返回类的jclass值

参数 env-JNI接口指针

name-待查找的类名

然后获取JniTest类的构造方法的ID,并保存在jmethodID变量中。在JNI本地函数中,若想使用过Java的方法,必须先获取该方法的ID。

jmethodID GetMethodID(JNIEnv * env,jclass clazz,const char * name,const char * signature)


说明 获取clazz类队形的指定方法的ID。注意,方法名name 与签名应当保持一致。若获取类构造方法的ID,方法名应为< init >,出错返回NULL

参数 env-JNI接口指针

clazz-Java类

name-方法名

signature-方法签名

如果是静态方法则可以调用JNI函数中的 GetStaticMethodID()函数,获得指定静态方法的ID

最后创建对象

以上面两部获取的JniTest类的jclass与构造方法的ID为参数,调用JNI函数NewObject(),生成JniTest类的对象

jobject NewObject(JNIEnv * env,jclass clazz,jmethodID methodID,...);


说明:生成指定类的对象。methodID指类的构造方法的ID,返回类对象的引用,出错返回NULL

参数:env-JNI接口指针

clazz:Java类

methodID:类的构造方法的ID

…:传递给类构造方法的参数

3调用Java方法

//1 获取含待调方法Java类的jclass
/*targetClass = env->GetObjectClass(newObject);*/

//2 获取调用对象的方法ID
mid = env->GetMethodID(targetClass,"callByNative","(I)I");
//3 调用Java方法 保存返回值
result = env->CallIntMethod(newObject,mid,200);


一般通过JNI调用Java方法的顺序就是上面代码的部分

1 获取含待调方法的Java类的jclass,由于上面例子中,咱们已经找到了JniTest类的jclass,所以此处不用该步

targetClass = env->FindClass("JniTest");//(1)


2 调用GetMethodID()函数,获取调用方法的ID(jMethodID)

3 根据返回值类型,调用相应的JNI函数,实现对Java方法的调用,如果是静态方法,则调用函数的形式为CallStatic< type >Method();若待调的方法属于某个类对象,则调用函数的形式应为Call< type >Method();

由于JniTest对象的callByNative()方法的返回值类型为int,所以在JNI函数中要调用CallIntMethod()函数。并且需要传入一个int型参数

< jnitype> CallStatic< type>Method(JNIEnv * env,jclass clazz,jmethodID methodID,...);


说明 调用methodID指定的类的静态方法

参数:env-JNI接口指针

clazz:含待调方法的类

methodID:类的构造方法的ID(由GetMethodID()函数获取)

…:传递给类构造方法的参数

返回值 被调方法的返回值

参考: < type >除了上面GetStatic< type >Field()中说明的九种以后,再此又添加了void类型,返回值类型< jnitype >也增加了void类型,待调方法的返回值不同,< type >也不同。若带掉方法的返回值类型为int,则调用函数为 CallIntMethod(),

相对应的还有

< jnitype> Call< type>Method(JNIEnv * env,jobject obj,jmethodID methodID,...);


调用methodID指定的Java对象的方法,obj是含待调方法的Java对象的引用

4 通过JNI设置成员变量的值

//设置newObject对象的intField
//获取JniTest类中intField的fieldID
fid = env->GetFieldID(targetClass,"intField","I");
printf("[CPP]JniTestintField200\n");
//将newObject对象中的intField设置为result
env->SetIntField(newObject,fid,result);


void SetStatic< type >Field(JNIEnv * env,jclass clazz,jfieldID fieldID,< type >value);


说明 设置fieldID指定的Java类静态成员变量的值为value

参数 env:JNI接口指针

clazz:含待设成员变量类的引用

fieldID:待设成员变量的ID(由GetStaticFieldID()函数获取)

value:指定设定值

与之对象的非静态成员变量

void Set< type >Field(JNIEnv * env,jobject obj,jfieldID fieldID,< type >value);


设置fieldID指定的Java对象的成员变量的值 obj:包含待设成员变量的Java对象的引用

学习过程中遇到一个很棘手的问题



碰到这个问题,在网上查了好久也解决不了,后来自己认真检查发现问题定位在本地函数NewObject()那个位置,分析其三个参数才发现是与签名相关的错误,我将签名改成默认的构造函数也就是()V果然通过,于是就去查看构造函数,刚开始没有发现什么错 感觉没什么,后来发现是我构造函数多了一个void,去掉之后再换成(I)V签名果然通过,如果大家在测试过程中发现这个错误,定位到这个位置了,不妨测试一个用默认构造函数,如果通过就是构造函数出问题了

下面是运行结果



本人新人,有错请指正!谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android