这是我参加更文应战的第2天,活动详情检查: 更文应战

点赞关注,不再走失,你的支撑对我意义严重!

Hi,我是丑丑。本文 GitHub Androi线程安全d-NoteBook 已收录,这儿有 Android 进阶成长路线笔记 &线程 博客,欢迎跟着彭丑丑一起成长。(联系办法数据结构教程第5版李春葆答案在 GitHub)

前语

  • 对于 Java /数组指针 Android 工程师来说,native 开发是向高工进阶的必经之路,也是面试中与竞争者 摆开距离 的利器!为了点亮 native 技能树,首战之地得是点亮 JNI(Jalinux中文乱码视频va Native数据结构c言语版 Interface,Java 本地接口)柱石符文。
  • 在这篇文章里,我将带你由浅入深地带你探究 JNI 编程。假如能帮上忙,请必须点赞加关注,这真的对我非常重要。
  • 本文相关代码能够从DemoH线程池面试题allHelloJni下载检查。

目录

NDK | 带你点亮 JNI 开发柱石符文 (一)


前置知识

这篇文章的内容会触及以下前置 / 相关知识,贴心的我都帮你预备好了,请享受数据结构课程规划~

  • C 言语复习笔记
  • C++ 复习数组的界说笔记
  • 为什么 Java 完成了渠道无关性?

1. 概述

1.1 JNI 处理了什么问题?

Java 规划 JNI 机制的目的是增强 Java 与本地代码交互的才干。 先说一下 Java 和本github地代数据结构题库及答案码的差异:咱们知道程序的运转环境 / 渠道主要是操作体系 + CPUlinux是什么操作体系,每个渠道有自己的本地库和 CPU 指令集。像 C/C++ 这样的本地言语会变编译为依赖于特定渠道的本地代码,不具有跨渠道的性质。反github是干什么的观 Java,在虚拟机和字节码的加持下,Java 就具有了跨线程池渠道的性质,但换个角度看,却linux体系装置导致 Java 与本地代数据结构码交互的才干较弱,本地渠道相关的特性无法充沛发挥出来。因而,就有线程和进程的差异是什么必要规划 JNI 机制来增强 Java 和本地代码交互的才干。

提示: 本地代码一般是 C/C数据结构严蔚敏++,但不限于 C/C++。

NDK | 带你点亮 JNI 开发柱石符文 (一)

1.2 JNI 的优势

  • 1、处理密布核算的功率问题,例如图像处理、OpenGL、游戏等场景都是在 native 完成;
  • 2、复用线程池原理现有的 C/C++ 库,例如 OpenCVgitee

1.3 JNI 献身了什么?

  • 1、本地言语不具有跨渠道的特性,必须为不同运转环境编译本地言语的部分;
  • 2、Java 和 native 相互调用的数组词功率比 Java 调用 Java 的功率低(留意:是调用功率低,不要和履行功率混淆);
  • 3、增加了工程的复杂度。

2. 第一个 JNI 程序

本节咱们经过一个简略的 HelloWorld 程序来展现 JNI 编程的根本流程。

2.1 JNI 编程的根本流程

  • 1、创立 HelloWorld.java,并声明git教程 native 办法 sayHi();
  • 2、运用 javac 指令编译源文件,生成 HelloWorld.cl线程ass 字节码文件;
  • 3、运用 javah 指令导出 HelloWorld.h 头文件,头文件里包含了本地办法的函数原型;
  • 4、运用 C数据结构知识点总结/C++giti轮胎 完成函数原型;
  • 5、编译本地代码,生成 Hello-World.so 动态库文件;
  • 6、在 Java 代码中调用 System.loadLibrary(..linux操作体系基础知识.) 加载 so 文件;
  • 7、运用 Java 指令运转 HelloWorld 程序。

NDK | 带你点亮 JNI 开发柱石符文 (一)

源码不在这儿展现了,你能够下载 Demo 检查,下载途径:HelloJni。这儿只展现 JNI 函数声明:

JNIEXPORT v数据结构课程规划oid JNICALL Java_com_xurui_hellojni_HelloWorld_sayHi (JNIEnv *, jobject);

2.2 细节解说

下面线程池的七个参数,我总结了新手容易疑惑的几个问题:

  • 问题 1:头文件为什么要加#ifndef #define #endif?

答:防止头文件被多个文件引证时重数据结构复编译,所以把头文件的内容数据结构教程第5版李春葆答案放在 #ifndef 和 #endif 中心。常见模板如下:

#ifndef <宏>
#define <宏>
内容......
#endif
  • 问题 2:为什么要运用 extern “C”linux必学的60个指令

答:exte数组c言语rn "C"表明即便处于 C+数据结构图+ 环境,也要悉数运用 C 的标准进行编译。咱们能够在 jni.h 文件中找到答案:由于 JNI 办法中的 JavaVM 和 JNIEnv 终究都调用到了 C 中的 JNIInvokeIntelinux体系装置rface_giti 和 JNINativeInterface_。(todo 论据不充沛)

jni.h

structlinux是什么操作体系 JNIEgiteenv_;
struct JavaVM_;
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
typedef JavaVM_ JavaVM;
#else
typedef con数据结构严蔚敏st struct JNINativeInterface数据结构课程规划_ *JNIEnv; // 结构体指针
typedef const struct JNIInvokeInterface_ *JavaVM; // 结构体指针
#endif
不管 C 仍是 C++,终究调用到 C 的界说
structlinux体系 JNIEnv_ {
const struct JNINativeInterface_ *fun线程池ctions;
......
}
struct JavaVM_ {
const structlinux常用指令 JNIInvokeInterface_ *functions;
......
}
  • 问题 3:为什么 JNI 函数名是 Java_com_xurui_HelloWorld_sayHi?

答:这线程撕裂者是 JNI 函数静态注册约好的函数命名规矩,当 Java 虚拟机调用 native 办法时,需求履行对应的 JNI 函数,而 JNI 函数注册评论的就是如何确定 native 办法与 JNI 函数之间的映射联系,有两种办法:静态注册Linux和动态注册。静态注册选用的是依据约好的命名规矩,无重载时选用「短称号」规矩,有重载时选用「长称号」规矩。更多详细的分析在我之前的一篇文章里评论过:NDK | 带你整理 JNI 函数注册的办法和机遇

  • 问题 4:关键词 JNIEXPORT 是什么意思?

答:J线程安全NIEXPORT 是一个宏界说,表明一个函数需数据结构严蔚敏要暴露给同享库外部运用时。JNIEXPOR数组词T 在 Wilinux必学的60个指令ndow 和 Linux 上有不同的界说:

Windows 渠道 :
#define JNIEXPORT __declspec(dllexp数组排序ort)
#define JNIIMPORT数据结构知识点总结 __declspeclinux指令(dllimplinux体系ort)
Linux 渠道:
#define JNIIMPORT
#define JNIEXPORT  __attribute__ ((visibility ("default")))
  • 问题 5:关键词 JNgiti轮胎ICA线程的几种状况LL 是什么意思?

答:JNICALL 是一个宏界说,表明一个函数是 JNI 函数。JNICALL 在 Wi数组指针ndow 和 Linux 上有不同的界说:

W数组初始化indows 渠道 :
#define JNICALL __stdcall // _Git_stdcall 是一种函数调用参数的约好 ,表明函数的调用参数是从右往左。
Linux 渠道:
#define JNICALL

问题 6:第一个参数 JNIEnv* 是什么?
答:第一个参数是 JNIEnv 指针,指向一个 JNI 函数表。经过这些 JNI 函数能够让本地代码拜访 Java数据结构 虚拟机的内部数据结构。JNIEnv 指针还有一个效果,就是屏蔽了 Java 虚拟机的内部完成细节,使得本地代码库能够透明地加载到不同的 Java 虚拟机完成中去(献身了调用功率)。

问题 7:第二个参数线程安全 jobject 是什么?
答:第二个参数依据 native 办法是静态办法仍是实例办法有所不同。对于静态 native 办法,第二个参数 jclass 代表 native 办法地点类的 Class 目标。对于实例 native 办法,第二个参数 jobjgiti轮胎ect 代表调用 native数据结构与算法 的目标。

2.3 类型的映射联系

Java 类型在 JNI 中都会映射为 JNI 类型,具体映射关数据结构教程第5版李春葆答案系界说在 jni.h 文件中,jbyte, jint 和 jlong 和运转环境有关,界说在 jni_md.h 文件中。总结如下表:

Java 类型 JNI 类型 线程池 长度(字节)
boolean jboolean unsigned char 1
charlinux体系装置 jchalinux中文乱码视频r unsigned short 2
short jshort signed short 2
float jfloat signed float 4
double jdouble signed double 8
int jint、jsize signed int 2 或 4
long jlong signed long 4 或 8(LP64)
byte jbyte signed char 1
Class jclass Java Class 类目标 /
String jstrting Java 字符串目标 /
Object jobject Ja线程和进程的差异是什么va 目标 /
byte[] jbyteArray byte 数组 /

3. JNI 调用 Java 代码

这一节咱们来评论如安在 JNI 中拜访 Java 字段和办法,在本地代码中拜访 Java 代码,需求运用 ID 来拜访字段或办法。频繁检索 ID 的进程相对耗时,一般咱们还需求缓存 ID 来优化功能的办法。

3.1 JNI 拜访 Java 字段

本地代码拜访 Java 字段的流程分为两步:数据结构课程规划

  • 1、经过 jclass 获取字段 ID,例如:Fid = env->GetFieldId(clz, "name", "Ljava/lang/String;");
  • 2、经过字段 ID 拜访字段,例如:Jstr = env->GetObjectField(thiz, Fid);

需求留意:Ljava/lang/String;是实例字段数据结构课程规划name的字段描述符,严格来说,所谓「字段描述符」其实是 JVM 字节码中描述字段的规矩,和 JNI 无直接联系。运用 javap 指令也能够自动生成字段描述符和linux体系装置办法描述符,An线程撕裂者dro数据结构图id Studigitlabo 也会协助自动生成。完好的字段描述符规矩如下表:

Java 类型 字段描述符
boolean Z
byte B数组公式
char C
short S
int I
long J
float F
double D
void V
引证类型 以 L 开头 ; 结束,中心是 / 分隔的包名和类名。
例如 String 的字段描述符为 Ljava/lang/String;

Java 字段分为静态字段和实例字段,本地代码获取或修正数据结构c言语版第二版课后答案 Javgitlaba 字段主要是运用以下 6 个办法:

  • GetFieldId:获取实例办法的字段 ID
  • GetStaticFieldId:获线程是什么意思取静态办法的字数组词段 ID
  • GetField:获取类型为 Type 的实例字段(例如 GetIntField)
  • SetFiel数组去重d:设置类型为 Type 的实例字段(例如 SetIntField)
  • GetStaticField:获取类型为 Type 的静态字段(例如 GetStaticIntField)
  • Se数据结构题库及答案tStaticField:设置类型为 Type 的静态字段(例如 SetStaticIntFiel数组和链表的差异d)

native-数组指针lib.cpp

extern "Cgitee"
JNIEXPORT void JNICALL
Javlinux体系装置a_com_xurui_hellojni_HelloWorld_linux体系装置accessField(JNIEnv *env, jobject thiz) {
// 获取 jclass
jclass clz = env->GetObjectClass(thiz);
// 静态字段 ID
jfieldID sFieldId = enlinux体系装置v-&gtgit教程;线程池创立的四种GetStaticFieldID(clz, "sgithub是干什么的Name", "Ljava/lang/String;");
// 拜访静态字段
if (sF数据结构知识点总结i线程是什么意思eldId) {
jstring jStr = s数组去重ta线程池的七个参数tic_cagithub永久回家地址st<jstringithubg>(env->GetStaticObjectField(clz, sFieldId));
// 转换为 C 字符串
const ch数据结构图ar *sStr = env->GetStringUTFChars(jStr, NULL);linux中文乱码视频
LOGD("静态字段:%s", sStr);
env->ReleaseStringUTFChars(jStr, sStr);
jstring newSt数据结构题库及答案r = env->NewStringUTF("静态字段 - Peng");
if (newStr) {
en线程和进程的差异是什么v-&g线程和进程的差异是什么t;SetStaticObjectField(clz, sFiellinux操作体系基础知识dId, newStr);
}
}
// 实例字段 ID
jfieldIlinux体系D mFieldId = env->GetFiel线程池创立的四种dID(clz, "mName", "Ljava/线程是什么意思lang/String;");
// 拜访实例字段
if (mFieldId) {
jstring jStr = staticgithub中文官网网页_cast<jstring>(env->GetObjectField(thiz, mFieldId));
// 转换为 C 字符串
const char *sStr = env->github永久回家地址GetStringUTFChars数组初始化(jStr, NULL);
LOGD(数组函数的运用办法"实例字段:%s", sStr);
env-&ggiteet;ReleaseStringUTFChars(jStr, sStr);
jstring newStr = env线程池->NewStringU线程是什么意思TF("实例字段 - Penlinux体系装置g");
if (newStr) {
env->SetObjectFiegitlabl数据结构c言语版d(thiz, mFieldId, newStr);
}
}
}

3.2 JNI 调用 Javlinux必学的60个指令a 办法

本地代码拜访 Java 办法与拜访 Java 字段相似,拜访流程分为两步:

  • 1、经过 jclass 获取「办法 ID」,例如:Mid = env-&gt线程和进程的差异是什么;Getgithub中文官网网页MethodID(jclass, "helloJ数据结构ava", "()V");
  • 2、经过办法 ID 调用办法,例如:env->CallVoidMetho数据结构d(thiz, Mid);

需求留意:()V是实例办法helloJava的办法描述符,严格来说「办法描述符」是 JVM 字节码中描述办法的规矩,和 JNI 无直接联系。

Java 办法分为静态办法和实例办法,本地代码调用 Java 办法主要是运用以下 5 个办法:

  • GetMethodId:获取实例办法 ID
  • GetStaticMethodId:获取静态办法 ID
  • CallMethod:调用回来类型为 Type 的实例办法(例如 GetVoidMethod)
  • CallStaticMethod:调用回来类型为 Type 的静态办法(例如 CallStaticVoidMethod)
  • CallNonvirtualMethod:调用回来类型为 Tylinux指令pe 的父类办法数组词(例如 CallNonvirtualVoidMethod)

native-lib.cpp

extern "C"数据结构知识点总结
JNIEXPORT void JNICALL
Java_com_xurui_hellojni_Hell线程oW线程撕裂者orld_accessMethod(JNIEnv *env, jobject thi数据结构z) {
// 获取 jclgit指令ass
jclass clz = env->GetObjectClass(thiz);
// 静态办法 ID
jmegiteethodID sMethodId = env->GetStaticMethodID(clz, "sHelloJava", "()V");
if (sMethodId) {
env->CallStaticVoidMethod(clz, sMethodId);
}
// 实例办法线程池面试题 ID
jmethodID mMethodId = env->GetMethodID(clz, "helloJava", "()V");
if (mMethodId) {
env->CallVoidMethod(thiz, mMethodGitId);
}
}

3.3 缓存 ID

  • 为什么要缓存 ID:拜访 Java 层字段或办法时,需求先利用字段名 / 办法名和linux中文乱码视频描述符进行检索,取得 jfieldID / jmethodID。这个检索进程数据结构题库及答案比较耗时,优化办法是将字段 ID 和办法线程的几种状况 ID 缓存起来,削减重复检索。数组c言语

  • 缓存 ID 的办法:缓存字段 ID 和 办法 ID的方github中文官网网页法主要有两种:运用时缓存 + 初始化时缓存,主要差异在于缓存产生的机遇和缓存 ID 的时效性。

运用时缓存:

运用时缓存是指数据结构c言语版在初次拜访字段或办法时,将字段 ID 或办法 ID 存储在静态变量中。这样在将来再次调用本地办法时,就不需求重复检索 ID 了。例如:

js数据结构tring MyNewString(JNIEnv* env, jchar* chars, jint len)数组和链表的差异 {
// 静态字段
static jm数组c言语ethodID cid = NULL;
jclass stringClazz = (*env)->FindClaslinux必学的60个指令s(env,"java/lang/String");数据结构c言语版第二版课后答案
if(NULL == cid) {线程是什么意思
cid = (*env)->GetMethodID(env,stringClazz,"<init>","([C)V");
}
jcharAr数据结构c言语版ray elemArr = (*env)->NewCharArray(env,len);
(*env)-&数组gt;SetCharArrayRegion(env, elemArr, 0, len, chars);
js数组tring result = (*env)->NewObject(env, s数据结构题库及答案tringClalinux操作体系基础知识zz, cid, elemArr);
(*env)->DeleteLocalRef(env,elemArr);
(*env)->DeleteLocalRef(env,st数据结构c言语版ringClazz);
return result
}

提示: 多个线程拜访这个本地办法,会运用相同的缓存 ID,会出现问题吗?不会,多个线程核算的字段 ID 或办法 ID 其实是相同linux的。

静态初始化时缓存:

线程态初始化时缓存是指在 Java 类初始化的时候,数据结构与算法提早缓存字段 ID 和办法 ID。例如:

private static native void initIDs();
static {
// Java 类初始化
System.llinux体系装置oadLibrar数据结构c言语版y("Ins数组指针tanc线程撕裂者eMethodCall");
initI数据结构Ds();
}
----------------------------------------------------
jmethlinux操作体系基础知识odID cid;
jmethoidID stringId;
JNIEXPORT void JNICALL
Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls) {
cid = (*env)->GetMethodID(env, cls, "callback", "()V");
jclass s数据结构题库及答案tringClazz = (*env)->Flinux重启指令indClass(env,"java/lang/String");
stringId = (*env)->GetMethodID(enlinux指令v,stringClazz数据结构严蔚敏第二版课后答案,"<init>","([C)V");
}

3.4 两种缓存 ID 办法的对比和运用场景

在大多数情况下,应该尽可能在静态初始化时缓存字段 ID 和办法 ID,由于运用时缓存存在一些局限性:线程池创立的四种

  • 1、每次运用前都要检数据结构知识点总结查缓存有用;
  • 2、字段 ID 和办法 ID 在 Java 类卸载 (ulinux指令nload) 时会失效,因而需求保证类卸载之后不会持续运用这个 ID。而静态初始化时缓存giti轮胎在类加载 (load) 时重新检索 ID,因而不用忧虑 IDlinux体系装置 失效。

当然,运用时缓存也不是一无是处。假如无法修正 Java 代码源码,运用时缓存是必然的选择。另一个优势在于,运用时缓存相当于懒初始化,能够按需检索 ID,而静态初始化时缓存相当于提早初始化,会一次性检索一切 ID。尽管如此,大多数情况下仍是gitee数据结构严蔚敏第二版课后答案运用静态初始化时缓存。

3.5 什么是 ID,什么是引证?

引证是经过本地代码来管理 JVM 中的资源,能够一起创立多个引证指向相同目标;而字段 ID 和办法 ID 由 JVM 管理,同一个字段或办法的 ID 是固定的,只有在所属类被卸载时会失效。


4. 加载 & 卸载 so 库的进程

关于加载与卸linux指令载 so 库的全进程,在我之前写过的一篇文章里讲过:《NDK | 说说 so 库从加载到卸载线程池面试题的全进程》。这儿我简略复述下:

  • 1、so 库加数组排序载到卸载的大体进程,主要linux重启指令分为:确定 so 库绝对途径、nlinuxativeLoad 加载进内存、ClassLoader 卸载时跟随卸载
  • 2、查找 so 库的途径,分为 App 途径(/data/app/[packagename]/数据结构严蔚敏第二版课后答案lib/arm64数据结构)和体系途径(/system/lib64、/vendor/lib64);
  • 3、J数组的界说NI_OnLoadJNI_OnUnLoad别离git教程在 so 库加载与卸载时履行。

NDK | 带你点亮 JNI 开发柱石符文 (一)


5. 注册 J线程池NI 函数

关于 JNI 函数注册的办法和机遇,在我之前写过的一篇文章里讲过:《NDK | 带你整理 JNI 函数注册的办法和机遇》。这儿我linux简略复述下:

  • 1、调用 Java 类中界说的 native 办法时,虚拟机会调用对应的 JNI 函数,而这些数组c言语 JNI 函数需求数组公式先注册才干运用。
  • 2、注册 JNI 函数的办法分为 静态注册 & 动态注册
  • 3、注册 JNI 函数有三种机遇:
注册的机遇 对应gitlab的注册办法
1、虚线程池创立的四种拟机第一次调用 nagititive 办法时 静态注册
2、Android 虚拟机启动时 动态注册
3、加载 so 库时 动态注册

6. JNIEnv * 和 JavaVM

6.1 JNgit指令IEnv * 指针的效果

JNIEnv* 指针指向一个 JNI 函数表,在本地代码中,能够经过这些函数来拜访 JVM 中的数据结构。从这个意义上说,能够理解为 JNIEnv* 指向了 Java 环境,但不能说 JNIEnv*Linux 代表 Java 环境。

NDK | 带你点亮 JNI 开发柱石符文 (一)

需求留意: 假如本地办法数据结构严蔚敏被不同的线程调用,传入的 JNIEnv 指针是数组排序不同的。JNIEnv 指针只在它地点的线程中有用,不能跨线程(乃至跨进程)传递和运用。但 JNIEngithub中文官网网页v 间接指向的函数表在多个线程间是同享的。

6.2 JavaVM 的效果

JavaVM 表明 Java 虚拟机,一个 J数据结构c言语版第二版课后答案ava 虚拟机对应一个 JavaVM 目标,这个目标是线程间同享的。咱们能够经过 JNIEnv* 来获取一个 JavaVM 目标:

jint GGitetJav数据结构知识点总结aVM(JNIEnv *env, JavaVM **vm);
- vm:用来存放取得的虚拟机的指针的指针线程池原理;
- return:成功回来0,失利回来其它。

6.3 在恣意方位获取 JNIEnv* 指针

JNIEnv* 指针仅在创立它的线程有用,假如咱们需求在其他线程拜访JVM,那么必须先调用linux是什么操作体系 AttachCurrentT数组和链表的差异hread 将github中文官网网页当时线程与 JVM 进行关联,然后才干取得JNIlinux中文乱码视频Env* 指针。别的,需求调用DetachCurrentTh线程池的七个参数read 来解除链接。

jintGit AttachCurrentThread(JavaVM* vm , JNIEnv** env , JavaVMAttaclinux操作体系基础知识hArgs* args);
- vm:虚拟机目标指针;数据结构严蔚敏第二版课后答案
- env:用来保存得到的 JNIEnv线程是什么意思 指针的指针;
- args:链接参数,参数结构体如下所示linux必学的60个指令;
- return:链接成功git教程回来 0,连接失线程池创立的四种败回来其它。
--------gitlab---------------------------
func() {
JNIEnv *env;
(*jvm)-linux体系>AttachCurrentThread(jvm, (void **)&env, NULL);
}数组函数的运用办法

7. 总结

今日咱们主要评论了 JNIgiti轮胎 编程的根本概念和运用进程,也评论了本地代码调用 Java 代码的进程git教程,也介绍了提高调用功率的办法 —— 缓存 ID。别的,关于 “加载 & 卸载 so 库的进程” 和 “JNI 函数注册的方线程是什么意思式和机遇” 咱们去年现已评论过了,希望能协助你建立对 JNI 编程的体系认知。后面,我后续会发布更多linux体系文章来评论 JNI 编程的高级概念,github是干什么的例如引证、多线程操作、异常处理等。记得关注~


参考资料

  • JNI 提示 —— Google Developers
  • 深化理解 JVM 字节码(第 10 章) —— 张亚 著
  • Java 功能威望攻略(第 7、8 章) —— [美]Scotlinux体系t Oaks 著
  • 《JNI 编程攻略》

创造不易,你的「三连」是丑丑最大的动力,咱们下次见!

NDK | 带你点亮 JNI 开发柱石符文 (一)