深入了解Android读书笔记——深入理解JNI

JNI概述

JNI是 Java Native Interface 的缩写,意为 Java本地接口。

作用:

  1. Java程序可以调用Native语言(一般指C/C++)写的函数
  2. Native语言可以调用Java层函数

在Android平台,JNI库采用 lib_模块名_jni.so 的命名方式。

JNI层必须实现为动态库的形式,这样Java虚拟机才能加载它并调用它的函数。

下面以Android源码的 MediaScanner 为例

加载动态库

static {
    //在linux上是 media_jni.so,在windows上是 media_jni.dll
    System.loadLibrary("media_jni");
    native_init();
}

一般加载动态库是在静态块中通过System.loadLibrary来实现的。

注册JNI

MediaScanner 对应的JNI代码在 android_media_MediaScanner.cpp 中。要使Java中的方法与JNI中的方法相对应,你需要注册JNI函数。有两种方式

静态注册(不推荐)

  1. 编写java文件,编译成 .class
  2. 使用 java -h 命令生成 .h 文件

实现原理:当Java层调用函数(如 native_init())时,它会从对应的JNI库寻找对应的函数(如 Java_android_media_Scanner_native_init()),如果没有就会报错。如果找到则会为这两个函数建立一个关联关系,其实就是保存JNI层函数的函数指针。以后调用这个函数时,直接使用就可以了。

不足:

  1. 需要编译所有声明了 native 的函数的java类,并且需要每个为它们生成一个 .h 文件
  2. javah 生成的JNI函数名特别长
  3. 初次调用会建立关系,影响运行效率

动态注册(推荐)

动态注册的结构


typedef struct {

    const char* name;//java的native方法的函数名

    const char* signature;//java的native方法的签名信息

    void* fnPtr;//JNI层对应函数指针

} JNINativeMethod;

示例代码如下

static const JNINativeMethod gMethods[] = {

...

    {
    "native_init",
    "()V",
    (void *)android_media_MediaScanner_native_init
    },
    {
    "native_setup",
    "()V",
    (void *)android_media_MediaScanner_native_setup
    },
    {
    "native_finalize",
    "()V",
    (void *)android_media_MediaScanner_native_finalize
    },
};

将结构注册的方法是

//这里的className是java类的全路径名
jclass clazz = (*env) ->FindClass(env, className);

//注册关联关系,Android中提供了JNIHelp,其内部有jniRegisterNativeMethods方法封装了这些步骤
(*env)->RegisterNatives(env, clazz, gMethods, numMethods);

当Java层通过System.loadLibrary加载完动态库后,会查找该库的JNI_OnLoad函数。如果有的话,就会调用它。因此我们需要在代码中实现这个函数,并在函数中调用注册结构的方法。

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    ...

    //注册关联关系
    (*env)->RegisterNatives(env, clazz, gMethods, numMethods);
    ...
    return JNI_VERSION_1_4;//必须返回这个值,否则报错
}

JNI层代码中一般要包含jni.h的头文件。Android源码中提供了JNIHelp.h的帮助头文件,它内部包含了jni.h。所以代码中直接包含JNIHelp.h即可

数据类型转换

基本数据类型转换表

Java Native类型 符号属性 字长
boolean jboolean 无符号 8位
byte jbyte 无符号 8位
char jchar 无符号 16位
short jshort 有符号 16位
int jint 有符号 32位
long jlong 有符号 64位
float jfloat 有符号 32位
double jdouble 有符号 64位

引用数据类型

Java引用类型 Native类型
all objects jobject
java.lang.Class jclass
java.lang.String jstring
Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbyteArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray
java.lang.Throwable jthrowable

JNIEnv

image.png

上图是 JNIEnv 的内部结构。从图中可知,JNIEnv 实际上就是提供了一些JNI系统函数。通过这些函数可以做到:

  1. 调用Java的函数

  2. 操作jobject对象等

JNIEnv与JavaVM

在一个线程中有一个JNIEnv,它是与线程相关的。而 JavaVM 在多线程中也只有一份。通过 JavaVMAttachCurrentThread 函数,就可以得到这个线程的 JavaVM 结构体;另外在退出线程时,需要调用DetachCurrentThread 来释放对应的资源

JNIEnv操作jobject

jfieldID 和 jmethodID

jfieldIDjmethodID 分别表示java类的成员变量和成员函数,可以通过 JNIEnv 的下面方式得到

//获取成员变量
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig)

//获取成员函数
jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)

其中jclass代表java类,name表示成员函数或者成员变量的名字,sig为这个函数或者变量的签名信息。

注意:获取java类的成员变量和成员函数是耗时操作,一般把获取到的java类的成员变量和成员函数对象保存到成员变量中,提高程序的运行效率

使用 jfieldID 和 jmethodID


NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...)

其中type 是指方法的返回值类型,如 CallVoidMethodCallIntMethod ;如果需要调用静态方法,你需要调用 CallStatic<Type>Method 系列函数。

//获取属性值
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID)

//设置属性值
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value)

jstring


NewString()//从Native字符串得到一个jstring对象

NewStringUTF()//根据Native的一个UTF-8字符串得到一个jstring对象

GetStringChars()//将java string 转换成Unicode字符串

GetStringUTFChars()//将java string转换成UTF-8字符串

ReleaseStringChars()//释放资源,否则会导致JVM内存泄露

ReleaseStringUTFChars()//释放资源,否则会导致JVM内存泄露

JNI类型签名

Java提供一个叫javap的工具帮助生成函数或者变量的签名信息,用法如下:

javap -s -p xxx

其中 xxx 为编译后的class文件,s表示输出内部数据类型的签名信息,p表示打印所有函数和成员的签名信息。默认只会打印public成员和函数的签名信息。

垃圾回收

JNI的三种类型引用

  • 本地引用:在JNI层函数中使用的非全局引用对象都是 本地引用 ,它包括函数调用时传入的jobject和在JNI层函数中创建的jobject。本地引用的最大特点是,一旦JNI层函数返回,这些jobject就可能被垃圾回收
  • 全局引用:这种对象不主动释放,就永远不会被垃圾回收
  • 弱全局引用:是一种特殊的全局引用,它在运行过程中可能被垃圾回收。所以在使用之前,需要调用JNI的IsSameObject判断它是否被回收了
static jobject save_thiz = NULL;
save_thiz = jobject;//这样不会增加引用计数,可能被垃圾回收掉,因此需要全局引用

释放引用

DeleteLocalRef()//释放本地变量,当本地变量分配太多内存,而方法执行时间长时,需要处理
DeleteGlobalRef()//释放全局变量

JNI异常处理

如果调用JNI的函数出错了,则会产生一个异常,但这个异常不会中断本地函数的执行,直到从JNI层返回到Java层后,虚拟机才抛出这个异常。虽然在JNI层中产生的异常不会中断本地函数的运行,但一旦产生异常后,就只能做一些资源清理工作了。

JNI层函数可以在代码中捕获和修改这些异常

  • ExceptionOccured:用来判断是否发生异常
  • ExceptionClear:用来清理当前JNI层发生的异常
  • ThrowNew:用来向Java层抛出异常

JNI完整文档看 Java Native Interface Specification Contents

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容