这是我参加更文应战的第2天,活动详情检查: 更文应战
点赞关注,不再走失,你的支撑对我意义严重!
Hi,我是丑丑。本文 GitHub Androi线程安全d-NoteBook 已收录,这儿有 Android 进阶成长路线笔记 &线程 博客,欢迎跟着彭丑丑一起成长。(联系办法数据结构教程第5版李春葆答案在 GitHub)
前语
- 对于 Java /数组指针 Android 工程师来说,native 开发是向高工进阶的必经之路,也是面试中与竞争者 摆开距离 的利器!为了点亮 native 技能树,首战之地得是点亮 JNI(Jalinux中文乱码视频va Native数据结构c言语版 Interface,Java 本地接口)柱石符文。
- 在这篇文章里,我将带你由浅入深地带你探究 JNI 编程。假如能帮上忙,请必须点赞加关注,这真的对我非常重要。
- 本文相关代码能够从DemoH线程池面试题allHelloJni下载检查。
目录
前置知识
这篇文章的内容会触及以下前置 / 相关知识,贴心的我都帮你预备好了,请享受数据结构课程规划~
- 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++。
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 程序。
源码不在这儿展现了,你能够下载 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->git教程;线程池创立的四种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->线程和进程的差异是什么;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_OnLoad
与JNI_OnUnLoad
别离git教程在 so 库加载与卸载时履行。
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 环境。
需求留意: 假如本地办法数据结构严蔚敏被不同的线程调用,传入的 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 编程攻略》
创造不易,你的「三连」是丑丑最大的动力,咱们下次见!