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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011391629/article/details/79568048

这一篇主要学习理论知识

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层所处的位置如下图所示。
这里写图片描述

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层也是对此有区分的。

基本数据类型转换

Primitive Types and Native Equivalents
Java Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void not applicable

引用数据类型转换

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

  • jobject
    • jclass (java.lang.Class objects)
    • jstring (java.lang.String objects)
    • jarray (arrays)
      • jobjectArray (object arrays)
      • jbooleanArray (boolean arrays)
      • jbyteArray (byte arrays)
      • jcharArray (char arrays)
      • jshortArray (short arrays)
      • jintArray (int arrays)
      • jlongArray (long arrays)
      • jfloatArray (float arrays)
      • jdoubleArray (double arrays)
    • jthrowable (java.lang.Throwable objects)

今日单词:

  • 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)组件对象模型

参考资料

猜你喜欢

转载自blog.csdn.net/u011391629/article/details/79568048
今日推荐