Android JNIAndroid开发Android开发

《深入理解Android:卷1》- JNI层(一)

2018-03-24  本文已影响95人  Dufre

这一篇主要学习理论知识

JNI是什么

JNIJava Native Interface

WikiPedia:JNI是一个编程框架,使得虚拟机(JVM)的Java程序也可以调用C/C++写的本地应用/库。这里的本地应用/库指不同的操作系统有其编程的特殊性,例如同样是打开一个文件,Windows上的API会调用OpenFile函数,而Linux上的API会调用open函数。

JNI的作用

在Android平台上,Java层、JNI层、Native层所处的位置如下图所示。

1.png

JNI的意义

JVM运行在具体的平台上,所以JVM本身无法做到与平台无关。JNI技术可以对Java层屏蔽不同操作系统平台之间的差异。

JNI实现

Java层

对Java层来说,只需要两步就能完成两项工作就可以使用JNI:

如果Java调用native函数,需要一个位于JNI层的动态库来实现,然后调用System.loadLibrary方法。System.loadLibrary函数的参数是动态库的名字,系统会自动的根据不同的平台转换成真实的动态库文件名,例如在Linux系统上会转换成pkg_Cls.so,而在Windows平台上会转换成pkg_Cls.dll。

package pkg; 

class Cls {
native double f(int i, String s);
static {
    System.loadLibrary(“pkg_Cls”);
    }
}

Native层

Java层中,有一个函数native double f(int i, String s),这个函数是在Native层中的jdouble Java_pkg_Cls_f__ILjava_lang_String_2函数实现的。

C函数:

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
     JNIEnv *env,        /* interface pointer */
     jobject obj,        /* "this" pointer */
     jint i,             /* argument #1 */
     jstring s)          /* argument #2 */
{
     /* C语言中用char指针获取Java的String */
     const char *str = (*env)->GetStringUTFChars(env, s, 0);

     /* process the string */
     ...

     /* Now we are done with str */
     (*env)->ReleaseStringUTFChars(env, s, str);

     return ...
}

C++函数中没有间接层和接口指针,但和C的底层实现原理一样,JNI函数会定义为内联成员函数。(why?)

extern "C" /* specify the C calling convention */ 

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* interface pointer */
     jobject obj,        /* "this" pointer */
     jint i,             /* argument #1 */
     jstring s)          /* argument #2 */

{
     const char *str = env->GetStringUTFChars(s, 0);

     // ...

     env->ReleaseStringUTFChars(s, str);

     // return ...
}

可以看出,native方法中:

JNI层

JNI层主要的工作是建立Java层和Native层的联系,即Java的f函数和C的Java_pkg_Cls_f__ILjava_lang_String_2函数。建立Java层和Native层联系(JNI函数注册)的方法有静态和动态两种方法。

静态方法

以f为例,当Java层调用f函数时,会从对应的JNI库中寻找其对应的函数Java_pkg_Cls_f__ILjava_lang_String_2,如果没有,就会报错。如果有则会为Java层的f和JNI层的Java_pkg_Cls_f__ILjava_lang_String_2建立联系,其实就是保存JNI层函数的函数指针。以后再调用时,直接使用函数指针即可。

静态方法的弊端:

动态方法

在JNI技术中,JNINativeMethod结构体会保存,Java中的native函数和JNI层函数的这种一一对应关系。

typedef struct{
    //Java中的native函数的名字
    const char* name;
    //Java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合
    const char* signature;
    //JNI层对应函数的函数指针
    void* fnPtr;
}JNINativeMethod;

然后通过AndroidRunTime类提供了一个registerNativeMethods函数来完成注册函数。

int registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)

而这个函数又是调用jniRegisterNativeMethods函数完成注册工作。

int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    ...
    //实际上是调用JNIEnv的RegisterNatives函数完成注册的
    if((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0)
    {
        return -1;
    }
    return 0;
}

总的来说,动态注册只用两个函数就能完成。

//找到对应的Java类。env指向一个JNIEnv结构体(稍后说明),className为对应的Java类名
jclass clazz = (*env)->FindClass(env, className);
//调用JNIEnv的RegisterNatives函数,注册关联关系。nMethods为methods数组中native函数的个数
jint RegisterNatives(JNIEnv *env, jclass clazz,const JNINativeMethod *methods, jint nMethods);

当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中的JNI_OnLoad函数,如果有就调用。

//JavaVM是虚拟机在JNI层的代表,每个Java进程只有一,
jint JNI_OnLoad(JavaVM *vm, void *reserved);

数据类型转换

Java数据类型分为基本数据类型和引用数据类型两种,JNI层也是对此有区分的。

基本数据类型转换

2.png

引用数据类型转换

除了基本类型的数组、Class、String和Throwable外,其余所有Java对象的数据类型在JNI中都用jobject表示。

3.png

今日单词:

参考资料

上一篇下一篇

猜你喜欢

热点阅读