JNI 与JNIEvn

JNI 开发是android NDK 开发的重要组成部分。 JNI 允许扩展android 底层代码,与android底层通信。

下面介绍一些本人对于JNI 开发的一些理解。

一 JNIEnvJavaVM

JNI 开发离不开JNIEnv 和JavaVM。JavaVM是虚拟机在JNI层的代表,每一个虚拟机进程只有一个javaVM。也就是说, 对于我们的JNI 来说, JavaVM 是一个全局变量。 

JNIEnv 是与线程有关的量,不同线程的JNIEnv彼此独立。

有些认为JNIEnv是Java调用其他语言(通常是C/C++)的环境。

JNI需要经常使使用指针javaVM 和JNIEnv 。但是在C与C++ 中的JavaVM 和JNIEnv 的定义和使用是不同的。

首先来看一下jni.h中javaVM 和JNIEnv的定义 。(androidNDK\android-ndk32-r10b-windows-x86_64\android-ndk-r10b\platforms\android-19\arch-arm\usr\include\jni.h)

可以看到

/*
 * JNI invocation interface.
 */
struct JNIInvokeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;

    jint        (*DestroyJavaVM)(JavaVM*);
    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
    jint        (*DetachCurrentThread)(JavaVM*);
    jint        (*GetEnv)(JavaVM*, void**, jint);
    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};

/*
 * C++ version.
 */
struct _JavaVM {
    const struct JNIInvokeInterface* functions;

#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
对于 C,JavaVM 是个指向函数指针结构体的指针。 而对于C++,JavaVM 是一个 函数结构体。 JNIEnv 的定义与JavaVM 类似。

对于C语言,必须先对envvm间接寻址。在调用方法时要将env 或者vm作为第一个参数。C++则直接利用envvm指针调用成员。

所以,JavaVM 和JNIEnv 的使用方法如下:

1. 对于C

(*env)->方法名(env,参数列表)

(*vm)->方法名(vm,参数列表)

2. 对于C++

env->方法名(参数列表)

vm->方法名(参数列表)

C++中定义了__cplusplusC语言中没有该定义。即:用__cplusplus来识别C代码还是C++代码。


二 注册native函数

1.      JNINativeMethod

JNINativeMethod是一个结构体。 Android系统中使用了一个映射表数据来注册和定义native函数的对应关系,并且描述了函数的参数和返回值。

而这个映射表的元素就是JNINativeMethod

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

第一个变量nameJava文件中的native函数的名字,

第二个变量signature用字符串描述了native函数的参数和返回值

第三个变量是函数指针,指向了对应的C函数。

比如以下的结构体

static const JNINativeMethod nativeMethods[] =
        {
                { "switchMode", "(IZ)Z",
                        (void *) Java_com_interfaces_EngineeringManager_switchMode },
                { "getSwitchMode", "(I)Z",
                        (void *) Java_com_interfaces_EngineeringManager_getSwitchMode },

};


函数 Java_com_interfaces_EngineeringManager_switchMode 对应于Java中的switchMode 方法。

其中第二列参数括号中描述了函数参数,括号后面代表返回值。 

"()V"就表示void Func();

"(II)V" 表示 void Func(int, int);

上面的(IZ)Z  表示 boolean switchMode(int, boolan);


具体的每一个字符的对应关系如下

typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;

数组则以"["开始,用两个字符表示

[I       jintArray     int[]
[F     jfloatArray    float[]
[B     jbyteArray    byte[]
[C    jcharArray    char[] 

[D    jdoubleArray double[]
[J     jlongArray     long[]
[Z    jbooleanArray boolean[]

 

三 JNI_OnLoad()

实现JNI中本地函数的注册有两种方式,

1.采用默认的本地函数注册流程。

2.自己重写 JNI_Onload() 函数

当系统调用到System.loadLibrary()时,androidVM会执行C库中的JNI_Onload()函数。

JNI_OnLoad()函数的主要作用

1.设置JNI版本,默认是使用最老的版本。

2.获得初始的开发环境,JavaVMJNIEnv*jint result

static int register_EngineeringManager(
        JNIEnv* env) {
    jclass clazz = (*env)->FindClass(env, CLASS_PATCH);

    if (clazz != NULL) {
        if ((*env)->RegisterNatives(env, clazz, nativeMethods,
                sizeof(nativeMethods) / sizeof(nativeMethods[0])) == JNI_OK) {
            return JNI_OK;
        }
    }
    return JNI_ERR;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    LOGI("JNI_OnLoad");
    jvm = vm;
    JNIEnv* env = NULL;
    jint result = JNI_ERR;

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) == JNI_OK) {
        if (NULL != env
                && register_EngineeringManager(
                        env) == JNI_OK) {
            result = JNI_VERSION_1_6;
        }
    }
    return result;
}

 

以上函数在JNI.h中都有定义。

 jclass clazz = (*env)->FindClass(env, CLASS_PATCH);
Class_PATCH 是一个字符串, 对应Javaclass的路径。

#define  CLASS_PATCH  "com/interfaces/EngineeringManager"
EngineeringManager.java 中定义了nativemethods[] 中的Java 方法。

 

四 重要方法。

   以下总结以下jni.h 中重要的方法, jni.h 定义了所有native 与java 交互的方法。

  JavaVM* jvm 的方法。

  JavaVM 的主要作用是获得JavaEnv。

    1. jint   (*GetEnv)(JavaVM*, void**, jint);     从java 虚拟机获得javaEnv。

第二个参数指向JavaEnv, 第三个参数为JNi 版本。

返回值小于0时说明没有得到JavaEnv。

    2.     jint    (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);    从当前线程获得javaEnv。

第三个参数指向线程名, 可以为空。

 

这两个方法经常一起使用, 当*GetEnv 方法得不到javaEnv 时, 从当前线程获得。

比如:

JavaVM* vm;

static JNIEnv *GetEnv()

{

int status;
JNIEnv *envnow = NULL;

    status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4);

    if(status < 0)

    {

        status = g_JavaVM->AttachCurrentThread(&envnow, NULL);

        if(status < 0)

        {

            return NULL;

        }
    }

    return envnow;

}

JavaEnv 的方法
JNIEnv* env;

env 的主要作用其实也就是和java 层交互。

1.      jclass      (*FindClass)(JNIEnv*, const char*);

    这个方法是要获得java 部分对应的类对象。 第二个参数是java类的路径。

比如:

        jclass tmp = (*env)->FindClass(env, “com/interfaces/EngineeringInterfaces”);

 temp 对应的是EngineeringInterfaces 类。

和此方法类似的还有方法    jclass      (*GetObjectClass)(JNIEnv*, jobject);

GetObjectClass 是从java类的一个引用获得一个jclass 对象。

而FindClass 是从java类的路径获得一个jclass 对象。

2.      jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); 

      比如: jmethodID construction_id = (*env)->GetMethodID(env, tmp, "<init>", "()V");

       获得对应类的构造函数的方法ID,“<init>”  表示构造函数, 第四个参数对应构造函数的参数和返回值。 此构造函数没有参数, 没有返回。

3.    jobject     (*NewObject)(JNIEnv*, jclass, jmethodID, ...);

       在JNI 构造 对应 java 类的对象。省略号表示的是构造函数需要的参数。

      例如

       jobject newObject= (*env)->NewObject(env, tmp, construction_id );

      就在JNI本地函数构造了EngineeringInterfaces 对象 。


       下面就可以使用EngineeringInterfaces对象中的方法和变量了。

4.     首先 需要获得java 类中对应方法的jmethodID 和对应变量的jfieldID。

       1)jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

         这个方法是要获得类中方法的jmethodID。 第二个参数是类, 第三个参数对应于方法名。 第四个参数是方法的参数和返回值。

比如

        mUpdateMethod = (*env)->GetMethodID(env, tmp, "dataChanged",   "(ILjava/lang/String;)V");

 mUpdateMethod 就对应于 EngineeringInterfaces.java 中的以下方法。

    public void dataChanged(int , String );

注意, 找到的方法必须是public 的。

其实第二步获得构造函数也是用的这个方法。

         2)方法:  jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*); 

 用于获得Java 类中的对应变量的jfieldID, 第三个参数是变量名, 第三个参数是变量的类型

         例如; jfieldID fieldID=(*env)->GetFieldID(env, tmp, "intField", I);

         fieldID 对应于Java类中的 int  intField;


5.  使用对应的方法

 void   (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);

这个方法的作用是调用java 对象中的方法。 其实也就是java 与native 通信的方法。调用的方法没有返回值

第二个参数是 其实就是java 对象,第三个参数是java 中调用方法的jmethodID。 以后省略的是传递给java  方法的参数。

比如:    (*env)->CallVoidMethod(env, newObject, mUpdateMethod , method_id, update_data);

  其实相当于调用方法:

                dataChanged(method_id,update_data);

  与此方法类似, 还有一个调用Java中静态方法方式

    void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);

类似还有

    1)jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); 方法返回boolean

    2)  jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);       返回byte

    3)   jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  返回char

    4)  jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);   返回int

    5)  jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...)  返回long

    6)  jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  返回对象



6 获得对应的变量

           jint        (*GetIntField)(JNIEnv*, jobject, jfieldID);

            第二个参数是对应的对象, 第三个参数是变量的jfieldID。

例如:jint  intFied=(*env)->GetIntField(env,newObject, fieldID)

           获得是intField 值

相应还有类似方法

    jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
    jboolean    (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
    jbyte       (*GetByteField)(JNIEnv*, jobject, jfieldID);
    jchar       (*GetCharField)(JNIEnv*, jobject, jfieldID);
    jshort      (*GetShortField)(JNIEnv*, jobject, jfieldID);
    jint        (*GetIntField)(JNIEnv*, jobject, jfieldID);
    jlong       (*GetLongField)(JNIEnv*, jobject, jfieldID);
    jfloat      (*GetFloatField)(JNIEnv*, jobject, jfieldID) __NDK_FPABI__;
    jdouble     (*GetDoubleField)(JNIEnv*, jobject, jfieldID) __NDK_FPABI__;

7.       设置变量

 void        (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);

第二个参数是对应的对象, 第三个参数是变量的jfieldID, 第四个参数为设置的值。

      例如:(*env)->SetIntField(env,newObject, fieldID, jint result);

   相当于 intField = result;

类似,还有相应的其他设置方法

    void        (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
    void        (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);
    void        (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);
    void        (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);
    void        (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);
    void        (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
    void        (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);
    void        (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat) __NDK_FPABI__;
    void        (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble) __NDK_FPABI__;


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值