[Android JNI] --- JNI基础

1 JNI概念

什么是JNI
JNI 全称 Java Native Interface,Java 本地化接口,可以通过 JNI 调用系统提供的 API。操作系统,无论是 Linux,Windows 还是 Mac OS,或者一些汇编语言写的底层硬件驱动都是 C/C++ 写的。Java和C/C++不同 ,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的Java字节码的.class文件,通过JIT技术即时编译成本地机器码,所以有效率就比不上C/C++代码,JNI技术就解决了这一痛点,JNI 可以说是 C 语言和 Java 语言交流的适配器、中间件。
JNI调用示意图:
在这里插入图片描述

什么是NDK?
NDK 全名Native Develop Kit,官方说法:Android NDK 是一套允许您使用 C 和 C++ 等语言,以原生代码实现部分应用的工具集。在开发某些类型的应用时,这有助于您重复使用以这些语言编写的代码库。

JNI与NDK区别
JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
NDK: NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发;

JNI与NDK中都有jni.h文件
jdk中jni.h路径
C:\Program Files\Java\jdk-1.8\include\jni.h路径
ndk中jni.h路径
C:\Users\Administrator\AppData\Local\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include
ndk对jdk的jni.h进行了封装,native c/c++使用的是ndk中的jni.h

JNI在Android中作用
JNI可以调用本地代码库(即C/C++代码),并通过 Dalvik 虚拟机与应用层和应用框架层进行交互,Android中JNI代码主要位于应用层和应用框架层;

  • 应用层: 该层是由JNI开发,主要使用标准JNI编程模型;
  • 应用框架层: 使用的是Android中自定义的一套JNI编程模型,该自定义的JNI编程模型弥补了标准JNI编程模型的不足;

2 JNI数据类型映射

在 JNI 开发中,Java 的数据类型并不是直接在 JNI 里使用的

2.1 基本数据类型

Java基本数据类型和本地类型的映射关系,这些基本数据类型都是可以直接在 Native 层直接使用的

Java数据类型 JNI本地类型 C/C++数据类型 数据类型描述
boolean jboolean unsigned char C/C++无符号8为整数
byte jbyte signed char C/C++有符号8位整数
char jchar unsigned short C/C++无符号16位整数
short jshort signed short C/C++有符号16位整数
int jint signed int C/C++有符号32位整数
long jlong signed long C/C++有符号64位整数
float jfloat float C/C++32位浮点数
double jdouble double C/C++64位浮点数

2.2 引用数据类型

引用数据类型和本地类型的映射关系:

Java数据类型 JNI本地类型 数据类型描述
java.lang.Object jobject 可以表示任何Java的对象,或者没有JNI对应类型的Java对象(实例方法的强制参数)
java.lang.String jstring Java的String字符串类型的对象
java.lang.Class jclass Java的Class类型对象(静态方法的强制参数)
Object[] jobjectArray Java任何对象的数组表示形式
boolean[] jbooleanArray Java基本类型boolean的数组表示形式
byte[] jbyteArray Java基本类型byte的数组表示形式
char[] jcharArray Java基本类型char的数组表示形式
short[] jshortArray Java基本类型short的数组表示形式
int[] jintArray Java基本类型int的数组表示形式
long[] jlongArray Java基本类型long的数组表示形式
float[] jfloatArray Java基本类型float的数组表示形式
double[] jdoubleArray Java基本类型double的数组表示形式
java.lang.Throwable jthrowable Java的Throwable类型,表示异常的所有类型和子类
void void N/A

需要注意的是:
1)引用类型不能直接在 Native 层使用,需要根据 JNI 函数进行类型的转化后,才能使用;
2)多维数组(含二维数组)都是引用类型,需要使用 jobjectArray 类型存取其值;

2.3 方法和变量ID

同样不能直接在 Native 层使用。当 Native 层需要调用 Java 的某个方法时,需要通过 JNI 函数获取它的 ID,根据 ID 调用 JNI 函数获取该方法;变量的获取也是类似。ID 的结构体如下:

struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID;     /* field IDs */

struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */```

3 JNI描述符

3.1 域描述符

基本类型描述符

在这里插入图片描述
Tips:
除了 boolean 和 long 类型分别是 Z 和 J 外,其他的描述符对应的类型名的大写首字母

扫描二维码关注公众号,回复: 15612015 查看本文章

引用类型描述符

一般引用类型描述符的规则如下,注意不要丢掉“;”

L + 类描述符 + ;

如,String 类型的域描述符为:

Ljava/lang/String;

数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号

[ + 其类型的域描述符

例如:

int[]    描述符为 [I
double[] 描述符为 [D
String[] 描述符为 [Ljava/lang/String;
Object[] 描述符为 [Ljava/lang/Object;
int[][]  描述符为 [[I
double[][] 描述符为 [[D

对应在 jni.h 获取 Java 的字段的 native 函数如下,name为 Java 的字段名字,sig 为域描述符

//C
jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
//C++
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    {
    
     return functions->GetFieldID(this, clazz, name, sig); }
jobject GetObjectField(jobject obj, jfieldID fieldID)
    {
    
     return functions->GetObjectField(this, obj, fieldID); }

3.2 类描述符

类描述符是类的完整名称:包名+类名,java 中包名用 . 分割,jni 中改为用 / 分割
如,Java 中 java.lang.String 类的描述符为 java/lang/String
native 层获取 Java 的类对象,需要通过 FindClass() 函数获取, jni.h 的函数定义如下:

//C
jclass  (*FindClass)(JNIEnv*, const char*);
//C++
jclass FindClass(const char* name)
    {
    
     return functions->FindClass(this, name); }

字符串参数就是类的引用类型描述符,如 Java 对象 cn.cfanr.jni.JniTest,对应字符串为Lcn/cfanr/jni/JniTest; 如下:

jclass jclazz = env->FindClass("Lcn/cfanr/jni/JniTest;");

3.3 方法描述符

方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:

(参数……)返回类型

例如:

  Java 层方法   ——>  JNI 函数签名
String getString()  ——>  Ljava/lang/String;
int sum(int a, int b)  ——>  (II)Ivoid main(String[] args) ——> ([Ljava/lang/String;)V

另外,对应在 jni.h 获取 Java 方法的 native 函数如下,其中 jclass 是获取到的类对象,name 是 Java 对应的方法名字,sig 就是上面说的方法描述符

//C
jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//C++
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    {
    
     return functions->GetMethodID(this, clazz, name, sig); }

不需要记住,可以使用下面方法查看签名

java -s -p xxx.class

4 静态注册和动态注册

当执行一个 Java 的 native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?这就需要用到注册的概念了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。
注册分为 静态注册 和 动态注册两种。

4.1 静态注册

定义
通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。

规则
java_完整包名_类名_方法名
特殊规则:

  • 包名或类名或方法名中含下划线 _ 要用 _1 连接;
  • 重载的本地方法命名要用双下划线 __ 连接;
  • 参数签名的斜杠 “/” 改为下划线 “_” 连接,分号 “;” 改为 “_2” 连接,左方括号 “[” 改为 “_3” 连接;
    另外,对于 Java 的 native 方法,static 和非 static 方法的区别在于第二个参数,static 的为 jclass,非 static 的 为 jobject;JNI 函数中是没有修饰符的。

示例

包名:com.android.javac,类名:NativeAccessJava

// java method
public native void setJavaString();

// jni method
JNIEXPORT void JNICALL
Java_com_android_javac_NativeAccessJava_setJavaString(JNIEnv *env, jobject thiz) 

4.2 动态注册

动态注册原理
动态注册 JNI 的原理:直接告诉 native 方法其在JNI 中对应函数的指针。通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系,步骤:
1> 先编写 Java 的 native 方法;
2> 编写 JNI 函数的实现(函数名可以随便命名);
3> 利用结构体 JNINativeMethod 保存Java native方法和 JNI函数的对应关系;
4> 利用registerNatives(JNIEnv* env)注册类的所有本地方法;
5> 在 JNI_OnLoad 方法中调用注册方法;
6> 在Java中通过System.loadLibrary加载完JNI动态库之后,会自动调用JNI_OnLoad函数,完成动态注册;

不再使用 JNIEXPORT 和 JNICALL 两个宏定义声明指定的方法,也不用依照固定的命名规则命名方法(不过通常 jni 里的方法名还是保持和 native 方法的方法名一致,见名思义),而是通过了一个 RegisterNatives 方法完成了动态注册。

函数映射表
JNINativeMethod这其实是一个结构体,在jni.h头文件中定义,通过这个结构体从而使Java与jni建立联系

typedef struct {
    
    
const char* name; //Java中函数的名字
const char* signature;//符号签名,描述了函数的参数和返回值
void* fnPtr;//函数指针,指向一个被调用的函数
} JNINativeMethod;

示例

// java method
    public native String stringFromJNI();
    public static native int add(int a, int b);
// jni register
jint DyReg::RegisterNatives(JNIEnv *env) {
    
    
    LOGE("RegisterNatives");
    jclass clazz = env->FindClass("com/android/javac/DynamicReg");
    if (clazz == NULL) {
    
    
        LOGE("could't find class: com/android/javac/DynamicReg");
        return JNI_ERR;
    }
    JNINativeMethod methods_MainActivity[] = {
    
    
            {
    
    "stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
            {
    
    "add",           "(II)I",                (void *) add}
    };
    // int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
    return env->RegisterNatives(clazz, methods_MainActivity,
                                sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}

猜你喜欢

转载自blog.csdn.net/weixin_42445727/article/details/131556768
JNI