JNI(1)---JNI入门介绍

102 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

JNI系列文章导引

JNI(1)---JNI入门介绍

JNI(2)--JNI函数使用(I)

JNI(3)--JNI函数使用(II)

JNI(4)--JNI中的api说明

JNI指南

介绍JNI的编写规则,方法注册以及数据类型。

题外话--我为什么写这个系列

JNI,在我刚入门Android的那时候,基本也就只是听说过,那时候都说JNI,那是中高级Android开发大佬们才能熟练运用的技能。从那时开始,JNI在我心里就是高级技术的象征。当然,那只是对于小白的我这么认为的,以至于工作几年后每每有迫不得已要用到时,都是到处胡乱查一通,然后放弃,交给其他同事处理,未免书到用时方恨少的感慨。

由于这个认知,每次想要学习这门”高深莫测“的技能时,都想着我一定要找一本系统学习的书,或者一门系统介绍的课程,从入门到精通。但是每次饱含热情去搜索,去google,最后发现,都是零散的几篇文章就介绍完毕了,就没了,没了....以至于我一直不敢下定决心掌握这门技术,以为自己认知到的只是片面,望而生畏。

工作几年后,对Android有了更深入的了解,在项目中看到同事的C代码,似乎也能从容淡定看待,也决心学习C,C++,掌握JNI这门技术。慢慢接触后发现,JNI真的也就如在网上看到的那般,内容可能并没有那么多,也并没有那么深奥难懂。

所以,我想写下这个系列,虽然也不长,有些也是借鉴了前人的经验,但是我想给看到的同学,想要了解JNI的同学打打气,JNI并不需要远远仰望,想学习掌握,随时都可以,不要怕,加油。

内容有不全,有疑问的地方,还请评论区指正,也欢迎一起探讨。

Hello JNI

编写JNIJava来调用C,一般包含下面几步:

  1. Java类中声明本地方法
  2. C代码实现声明的本地方法
  3. Java类中静态代码块调用System.loadLibrary加载so库

JavaHelloWorld

 package com.example.jnifirst
 class HelloWorld {
   fun main(args:Array<String>) {
     Log.d(TAG, stringFromJNI())
   }
 ​
   /**
    * jni生成字符串返回给java
    */
   private external fun stringFromJNI(): String
 ​
   companion object {
     const val TAG = "HelloWorld"
 ​
     // Used to load the 'jnifirst' library on application startup.
     init {
       System.loadLibrary("jnifirst")
     }
   }
 }

JNI类

 #include <jni.h>
 #include <string.h>
 ​
 /**
  * @param env 接口指针,JNI环境,指向一个个函数表,函数表中的每一个入口指向一个JNI函数。本地方法通过这些函数访问JVM中的数据结构
  * @param thiz java中定义这个方法的对象。会根据本地方法是一个静态方法还是实例方法有所不同。静态方法的话,会是本地方法所在的类
  * @return jstring java string对象
  */
 JNIEXPORT jstring JNICALL
 Java_com_example_jnifirst_HelloWorld_stringFromJNI(JNIEnv *env, jobject thiz) {
     char *str = "hello world from jni";
     //将c字符串转换成java字符串类型
     return (*env)->NewStringUTF(env, str);
 }

JNI中的函数会生成一个字符串返回给Java调用方

JNIEXPORTJNICALL这两个宏定义在jni.h中,确保整个函数在本地库外可见,并且C编译器会进行正确的调用转换

第一个参数env,接口指针,JNI环境,指向一个个函数表,函数表中的每一个入口指向一个JNI函数。本地方法通过这些函数访问JVM中的数据结构

第二个参数thizJava中定义这个方法的对象。会根据本地方法是一个静态方法还是实例方法有所不同。静态方法的话,会是本地方法所在的类;实例方法的话就是定义这个方法的对象。

JNI中与Java中函数名的对应的名称组成是:Java_package_className_methodName。其中package包名中的点“.”会转换成下划线“_”如上述Java_com_example_jnifirst_MyString_stringFromJNI

jstring代表了字符串,是JNI中对应的Java中的java.lang.String。所有的Java数据类型都会和JNI中数据类型相对应。

JNI数据类型

基本类型

JNI类型Java类型描述
jbytebyte有符号,8位,整型
jshortshort有符号,16位,整型
jintint有符号,32位,整型
jlonglong有符号,64位,整型
jfloatfloat32位,浮点型
jdoubledouble64位,浮点型
jbooleanboolean无符号,8位,整型
jcharchar无符号,16位,整型
voidvoidN/A

引用类型

JNI类型Java类型描述
jclassClass
jobjectObjectJava对象
jstringString字符串
jobjectArrayObject[]对象数组
jbyteArraybyte[]byte数组
jshortArrayshort[]short数组
jintArrayint[]int数组
jlongArraylong[]long数组
jfloatArrayfloat[]float数组
jdoubleArraydouble[]double数组
jbooleanArrayboolean[]boolean数组
jcharArraychar[]char数组
jthrowableThrowableThrowable

JNI方法注册

当执行一个native方法时,JVM需要直到该调用so中的哪个方法,这就需要用到方法注册了,通过注册,将native方法和so中的方法绑定起来,这样就能找到对应的方法了。

注册分为静态注和动态注册两种,默认是静态注册。

静态注册

如上述JNI中方法,就是使用Java_package_className_methodName,与Java中定义的native方法绑定起来。

 Java_com_example_jnifirst_MyString_stringFromJNI

动态注册

使用JNI中定义的RegisterNatives方法,进行方法注册,注册的时机是在JNI_OnLoad

 #include <jni.h>
 #include <string.h>
 ​
 JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     //获取env
     JNIEnv *env;
     if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
         return JNI_ERR;
     }
     //java中的类名,全限定
     char *className = "com/example/jnifirst/JniOnLoad";
     jclass c = (*env)->FindClass(env, className);
     if (c == NULL) {
         return JNI_ERR;
     }
     //声明本地方法数组,JNINativeMethod是JNI中的一个结构体
     static const JNINativeMethod method[] = {
             {"nativeFoo", "()V", (void *) nativeFoo}
     };
     // 注册本地方法
     // clazz:指定的类,即 native 方法所属的类
     // methods:方法数组,JNINativeMethod结构体数组
     // nMethods:方法数组的长度
     // 成功返回JNI_OK(0),失败返回负值
     int rc = (*env)->RegisterNatives(env, c, method, sizeof(method) / sizeof(method[0]));
     if (rc != JNI_OK) {
         return rc;
     }
     return JNI_VERSION_1_6;
 }

其中JNINativeMethod结构体定义如下:

 typedef struct {
     const char* name;  //native方法名
     const char* signature; // 方法签名
     void*       fnPtr; //函数指针
 } JNINativeMethod;

JNI类型签名

JNI的类型签名标识了一个特定的Java类型,这个类型可以是类、方法或数据类型。

类和对象的签名

组成为:L+包名+类名+;注意有一个“;” 。其中,包名中的“.”替换成“/”。

String类的签名就是:Ljava/lang/String;

基本数据类型的签名

一般就是基本数据类型的首字母大写,除了boolean是Z,long是J。对于void,则是V。

Java类型签名
byteB
shortS
intI
longJ
floatF
doubleD
booleanZ
charC
voidV

数组的签名

  • 一维数组

    一维数组签名就是[+数据类型

    Java类型签名
    Object[][Ljava/lang/Object
    String[][Ljava/lang/String
    byte[][B
    short[][S
    int[][I
    long[][J
    float[][F
    double[][D
    boolean[][Z
    char[][C
  • 二维数组

    二维数组签名就是[[+数据类型。如int[][]就是[[I

方法的签名

方法的签名包括了方法参数和返回值信息。组成为:(所有参数的类型)返回值类型。

 int fun1() ==> ()I
 ​
 void fun2(int i) ==> (I)V
 ​
 boolean fun3(int a, double b, String[] c) ==> (ID[Ljava/lang/String;)Z

参考资料:

www.jianshu.com/p/f803410da…