美文网首页Android JNIAndroid开发Android开发
《深入理解Android:卷1》- JNI层(一)

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

作者: Dufre | 来源:发表于2018-03-24 20:19 被阅读95次

    这一篇主要学习理论知识

    JNI是什么

    JNIJava Native Interface

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

    JNI的作用

    • Java程序中的函数可以调用Native语言写的函数,Native一般指C/C++编写的函数。
    • Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。

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

    1.png

    JNI的意义

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

    JNI实现

    Java层

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

    • 加载对应的JNI库
    • 声明由关键字native修饰的函数

    如果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方法中:

    • 第一个参数,类型是JNIEnv*,是一个指向JNIEnv的指针类型。
    • 第二个参数,如果申明的方法是static类型的,则该参数是一个jclass类型,如果申明的方法是一个非static类型的,则该参数是一个jobject类型。
    • 后面的参数是所声明方法的参数

    JNI层

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

    静态方法

    • 首先编写java代码,用javac编译生成.class文件
    • 使用 javah 命令生成与.class 文件对应的 .h 头文件
    • 实现.h文件中的函数
    • 使用ndk工具生成库文件(Linux为so文件,Windows为dll文件)

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

    静态方法的弊端:

    • 每个.class文件都要生成对应的.h文件
    • javah生成的JNI层函数名特别长,书写起来很不方便
    • 初次调用native函数时要根据函数名字找到其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

    今日单词:

    • JNI(Jave Native Interface): Java本地调用
    • JVM(Java Virtual Machine):Java虚拟机
    • platform-specific:特定平台
    • Native Libraries:本地库
    • Primitive Types:基本类型
    • Reference Types:引用类型
    • parameter: 形参(formal argument)
    • argument :实参(actual argument)
    • COM(component object model)组件对象模型

    参考资料

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/adkccftx.html