掌握 Android JNI 基础

写在前面

最近在看一些底层源码,发现 JNI 这块还是有必要系统的看一下,索性就写一写博客,加深加深印象🍻

本文重点聊一聊一些干货,避免长篇大论

JNI 概述

JNI 是什么?

定义:Java Native Interface ,即 Java 本地接口 作用:使得 Java 与本地其他类型语言(如 C、C++ )进行交互

注意:

  • JNI 是 Java 调用 Native 语言的一种特性

  • JNI 是属于 Java 的,与 Android 平台无直接关系

以 Java 8 为例,JNI 最新在线 API: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html

为什么会有 JNI ?

实际的使用中,Java 需要与本地代码进行交互,因为 Java 项目具备跨平台的特点,所以 Java 与本地代码交互的能力非常弱,采用 JNI 特性,增强 Java 与本地代码交互的能力

JNI 和 NDK 的关系

JNI 是 Java 平台提供的一套非常强大的框架 Java Native Interface,用于与本地代码进行相互调用

NDK 是 Android 平台提供的 Native 开发工具集,Native Development Kit的缩写。NDK 其中包含了 JNI 并对其进行了封装

关于 JNI 的入口头文件会有两份,分别在 JDK 和 NDK 中,进一步说明 Android 的 NDK 对 JDK 的 JNI 进行了二次封装

常见所在目录:

  • JDK: JAVA_HOME/include/jni.h

  • NDK: ~/Library/Android/sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/jni.h

环境准备

  • Android Studio 版本:Android Studio Hedgehog | 2023.1.1 Patch 1

  • Gradle 版本:gradle-8.0

  • targetSdk:33

Android Studio 安装相关工具:

创建示例

File -> New,选择 Native++ 模板:

检查 NDK 相关配置是否正常:

native-lib.cpp C++ 示例代码:

MainActivity.java 示例代码:

CMake 构建

在 Android 开发中,CMake 用于编译 C/C++ 代码。从 Android Studio 2.2 版本开始,Google 引入了对 CMake 的支持,使得开发者可以通过 CMake 和 NDK 将 C/C++ 代码编译成底层的库,然后再配合 Gradle 的编译将库打包到 APK 中

CMake 具体的配置信息如下:

# cmake最低版本要求
cmake_minimum_required(VERSION 3.22.1)# 配置库生成路径
# CMAKE_CURRENT_SOURCE_DIR是指 cmake库的源路径,通常是build/.../cmake/
# /../jniLibs/是指与CMakeList.txt所在目录的同级目录:jniLibs (如果没有会新建)
# ANDROID_ABI 生成库文件时,采用gradle配置的ABI策略(即:生成哪些平台对应的库文件)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})# 添加库
add_library( # 库名native-lib# 类型:# SHARED 是指动态库,对应的是.so文件# STATIC 是指静态库,对应的是.a文件# 其他类型:略SHARED# native类路径native-lib.cpp)# 查找依赖库
find_library(# 依赖库别名log-lib# 希望加到本地的NDK库名称,log指NDK的日志库log)# 链接库,建立关系( 此处就是指把log-lib 链接给native-lib使用 )
target_link_libraries(# 目标库名称(native-lib就是咱们要生成的so库)native-lib# 要链接的库(上面查找的log库)${log-lib})

摘自: https://www.cnblogs.com/qixingchao/p/11911787.html

默认 so 的目录(注意 AS 版本,不同版本不一致):

安装包反编译 so 路径位置:

示例解读

#include <jni.h>
#include <string>extern "C" JNIEXPORT jstringJNICALL
Java_org_lulu_jnilearning_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "这是 C++ 中的代码";char * str = "这是 C++ 中的代码";return env->NewStringUTF(str);
}
  • extern "C"避免按照 C++ 的方式去编译 C 函数

    为什么需要使用它呢? 这是因为 C++ 支持函数重载,所以编译器在编译函数时会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名。而 C 语言并不支持函数重载,因此编译 C 语言代码的函数时不会带上函数的参数类型,一般只包括函数名

  • JNIEXPORT :用来表示该函数是否可导出(宏定义)

  • JNICALL:(可以缺少)jni call 约束函数入栈顺序和堆栈内存清理规则,在 Linux 中置空

  • jstring:代表 Java 中的 String

  • JNIEnv:C 和 Java 相互调用的桥梁,代表 Java 环境,内部包含了众多函数(后续详细介绍)

  • jobject:( Java 侧声明的 native 方法为非静态,传递时为 jobject)Java 传递过来的示例对象,即当前 Java 类的对象,示例中 MainActivity.this 就是它

  • jclass:( Java 侧声明的 native 方法为静态,传递时为 jclass)Java 传递过来的类对象,即当前 Java 类的 Class 对象,示例中 MainActivity.class 就是它

JNIEnv

C 和 Java 相互调用的桥梁,代表 Java 环境

通过 JNIEnv 就可以对 Java 端的代码进行操作:

  • 创建 Java 对象

  • 调用 Java 对象方法

  • 获取 Java 对象的属性

  • ...

C++ 中 JNIEnv 指向_JNIEnv,而_JNIEnv是定义的一个结构体,包裹了 JNINativeInterface,而在 C 中JNIEnv就是 JNINativeInterface

常用的方法:

函数名称作用
NewObject创建Java类中的对象
NewString创建Java类中的String对象
NewArray创建类型为Type的数组对象
GetField获得类型为Type的字段
SetField设置类型为Type的字段
GetStaticField获得类型为Type的static的字段
SetStaticField设置类型为Type的static的字段
CallMethod调用返回值类型为Type的static方法
CallStaticMethod调用返回值类型为Type的static方法
FindClass通过类路径获取 Java 类的类对象
GetObjectClass通过类对象获取 Java 类的类对象

Java、JNI、C/C++ 基本类型映射

Java 、C/C++都有一些常用的数据类型,分别是如何与JNI类型对应的呢?做以下整理:

JNI中定义的别名Java类型C/C++类型
jint / jsizeintint
jshortshortshort
jlonglonglong / long long (__int64)
jbytebytesigned char
jbooleanbooleanunsigned char
jcharcharunsigned short
jfloatfloatfloat
jdoubledoubledouble
jobjectObject_jobject*

JNI描述符(签名)

JNI 开发时,我们除了写本地C/C++实现,还可以通过JNIEnv *env 调用 Java 层代码,如获得某个字段、获取某个函数、执行某个函数等:

//获得某类中定义的字段id
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig){ return functions->GetFieldID(this, clazz, name, sig); }//获得某类中定义的函数id
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig){ return functions->GetMethodID(this, clazz, name, sig); }

以上函数和 Java 的反射比较类似,参数说明:

  • clazz:类的 class 对象

  • name:字段名称、函数名称

  • sig:字段描述符(字段签名),函数描述符(函数签名)

特别的,对 sig 进行解释:

  1. 如果是字段,表示字段类型的描述符

  2. 如果是函数,表示函数结构的描述符,即:每个参数类型描述符 + 返回值类型描述符

后面会结合实际案例进一步说明

整理字段类型签名:

Java类型字段描述符(签名)备注
intIint 的首字母、大写
floatFfloat 的首字母、大写
doubleDdouble 的首字母、大写
shortSshort 的首字母、大写
longLlong 的首字母、大写
charCchar 的首字母、大写
byteBbyte 的首字母、大写
booleanZ因 B 已被 byte 使用,所以 JNI 规定使用 Z
objectL + /分隔完整类名String 如: Ljava/lang/String
array[ + 类型描述符int[] 如:[I

整理函数类型签名:

Java函数函数描述符(签名)备注
voidV无返回值类型
Method(参数字段描述符...)返回值字段描述符int add(int a,int b) 如:(II)I

如何通过指令获取当前 class 的签名:

找到此目录: 执行以下命令:

javap -s -p MainActivity.class 

相关描述符信息如下:

Compiled from "MainActivity.kt"
public final class org.lulu.jnilearning.MainActivity extends androidx.appcompat.app.AppCompatActivity {//...private java.lang.String nonStaticField;descriptor: Ljava/lang/String;private static java.lang.String staticField;descriptor: Ljava/lang/String;//...
}

Java和C++交互示例

我们以从 Native 侧修改 Java 侧非静态字段和静态字段为例,编写一段简单 Java 和 C++ 交互的示例:

修改非静态字段:

MainActivity.kt

private const val TAG = "MainActivity"class MainActivity : AppCompatActivity() {/*** 待修改的非静态字段*/private var nonStaticField = "Java 非静态字段"override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//...Log.d(TAG, "changeNonStaticField before: $nonStaticField")changeNonStaticField()Log.d(TAG, "changeNonStaticField after: $nonStaticField")}/*** 修改非静态字段的 Native 方法*/external fun changeNonStaticField()companion object {// Used to load the 'jnilearning' library on application startup.init {System.loadLibrary("jnilearning")}}//...
}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_org_lulu_jnilearning_MainActivity_changeNonStaticField(JNIEnv *env, jobject thiz) {//1. 获取当前实例的类对象,即 MainActivity.class//   有两种方式// 1.1 jclass FindClass(const char* name)//jclass clazz = env->FindClass("org/lulu/jnilearning/MainActivity");// 1.2 【推荐】jclass GetObjectClass(jobject obj)jclass clazz = env->GetObjectClass(thiz);//2. 获取要修改的非静态字段 Id//jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)jfieldID nonStaticFieldId = env->GetFieldID(clazz, "nonStaticField", "Ljava/lang/String;");//3. 创建一个新的 jstring,准备赋值//jstring NewStringUTF(const char* bytes)jstring jstr = env->NewStringUTF("Java 非静态字段,C++ 已修改");//4. 这是这个新的 jstring 给 nonStaticField 字段//void SetObjectField(jobject obj, jfieldID fieldID, jobject value)env->SetObjectField(thiz, nonStaticFieldId, jstr);
}

代码执行结果如下:

changeNonStaticField before: Java 非静态字段
changeNonStaticField after: Java 非静态字段,C++ 已修改

静态变量的修改类似,仅贴出C++代码:

extern "C"
JNIEXPORT void JNICALL
Java_org_lulu_jnilearning_MainActivity_changeStaticField(JNIEnv *env, jobject thiz) {//1. 获取当前实例的类对象,即 MainActivity.class//   有两种方式// 1.1 jclass FindClass(const char* name)//jclass clazz = env->FindClass("org/lulu/jnilearning/MainActivity");// 1.2 【推荐】jclass GetObjectClass(jobject obj)jclass clazz = env->GetObjectClass(thiz);//2. 获取要修改的静态字段 Id//jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)jfieldID staticFieldId = env->GetStaticFieldID(clazz, "staticField", "Ljava/lang/String;");//3. 创建一个新的 jstring,准备赋值//jstring NewStringUTF(const char* bytes)jstring jstr = env->NewStringUTF("Java 静态字段,C++ 已修改");//4. 这是这个新的 jstring 给 staticField 字段//void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)env->SetStaticObjectField(clazz, staticFieldId, jstr);
}

最后

这篇文章就到这里了,希望大家能够学到一些有用的知识,也欢迎你们在评论区留言交流。如果你觉得这篇文章有趣或者有帮助,不妨给我点个赞或者分享给你的朋友。感谢你们的阅读,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/657510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用GPT写PHP框架

参考https://www.askchat.ai?r237422 写一个mvc框架 上面是简单的案例&#xff0c;完整的PHP框架&#xff0c;其核心通常包含以下几个关键组件&#xff1a; 1. 路由&#xff08;Routing&#xff09;&#xff1a;路由组件负责解析请求的URL&#xff0c;并将其映射到相应的控制…

Kotlin快速入门系列9

Kotlin对象表达式和对象声明 对象表达式 有时&#xff0c;我们想要创建一个对当前类有些许修改的对象同时又不想重新声明一个子类。如果是Java&#xff0c;可以用匿名内部类的概念来解决这个问题。kotlin的对象表达式和对象声明就是为了实现这一点(创建一个对某个类做了轻微改…

使用Mysql实现Postgresql中窗口函数row_number的功能

1. 描述 需要根据用户id&#xff0c;查询每个人得分第二高的科目信息 2. 表结构及数据 2.1 表结构 CREATE TABLE t_score (id bigint(20) NOT NULL AUTO_INCREMENT,user_id bigint(20) NOT NULL,score double NOT NULL,subject varchar(100) NOT NULL,PRIMARY KEY (id) ) E…

已解决org.springframework.dao.DuplicateKeyException异常的正确解决方法,亲测有效!!!

已解决org.springframework.dao.DuplicateKeyException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 文章目录 问题分析 报错原因 解决思路 解决方法 识别违反的约束 审查与修改数据操作逻辑 优化数据处理流程 总结 在使用Spring框架…

在 Amazon EKS 上部署生成式 AI 模型

导言 生成式 AI 正在改变企业的运作方式&#xff0c;并加快创新的步伐。总体而言&#xff0c;人工智能正在改变企业利用技术的方式。生成式 AI 技术包括微调和部署大型语言模型&#xff08;LLM&#xff09;&#xff0c;并允许开发人员访问这些模型以执行提示和对话。负责在 Kub…

一篇文章,彻底理解数据库操作语言:DDL、DML、DCL、TCL

最近与开发和运维讨论数据库账号及赋权问题时&#xff0c;发现大家对DDL和DML两个概念并不了解。于是写一篇文章&#xff0c;系统的整理一下在数据库领域中的DDL、DML、DQL、DCL的使用及区别。 通常&#xff0c;数据库SQL语言共分为四大类&#xff1a;数据定义语言DDL&#xf…

网络安全产品之认识防病毒软件

文章目录 一、什么是计算机病毒二、什么是防病毒软件三、防病毒软件的作用四、防病毒软件的工作原理五、防病毒软件的核心技术六、防病毒软件的使用方式 随着计算机技术的不断发展&#xff0c;防病毒软件已成为企业和个人计算机系统中不可或缺的一部分。防病毒软件是网络安全产…

【String、StringBuffer和StringBuilder的区别及使用场景】

String、StringBuffer和StringBuilder的区别及使用场景 1. String类是不可变的&#xff0c;一旦创建&#xff0c;就不能修改。每次对String进行操作&#xff08;如拼接、替换等&#xff09;&#xff0c;实际上是创建了一个新的String对象。由于String的不可变性&#xff0c;频繁…

使用 Python 的 Matplotlib 库来绘制简单的爱心图案

import matplotlib.pyplot as plt import numpy as npt np.linspace(0, 2*np.pi, 100) x 16 * np.sin(t)**3 y 13 * np.cos(t) - 5 * np.cos(2*t) - 2 * np.cos(3*t) - np.cos(4*t)plt.plot(x, y, r) plt.axis(equal) plt.fill(x, y, r) plt.show()这段代码首先导入了 Matpl…

【java中如何避免死锁及其分析和解决多线程环境下的死锁问题】

java中如何避免死锁及其分析和解决多线程环境下的死锁问题 死锁是在多线程环境中经常遇到的一种问题&#xff0c;可以通过以下方法来避免和解决死锁问题&#xff1a;死锁是多线程环境下常见的问题&#xff0c;它发生在两个或多个线程等待对方释放资源的情况下。为了避免死锁&am…

uniapp H5 touchstart touchend 切换背景会失效,或者没用

uniapp H5 touchstart touchend 切换背景会失效&#xff0c;或者没用 直接上代码 &#xff08;使用 class 以及 hover-class来设置样式&#xff09; class 设置默认的背景图或者样式 hover-class 来设置按下的背景图 或者样式 抬起 按下 <view class"mp_zoom_siz…

NRF24L01无线 2.4G射频模块(学习笔记)

一、市场上的NRF24L01模块有三种 二、模块的引脚接口 标准的4线SPI接口 三、寄存器操作命令以及寄存器地址 四、两个NRF24L01模块能够成功通信需要满足的条件 五、两个NRF24L01模块通信连接示意图

git远程仓库基本操作

目录 gitremote &#xff08;查看远程仓库&#xff09; git remote add [仓库名] [url] git clone [url]&#xff08;克隆远程仓库到本地&#xff09; git push [名][分支名]&#xff08;提交到远程仓库&#xff09;​编辑 git pull [名][分支名]从远程仓库拉取​编辑 注意操作…

人工智能是哪个专业

人工智能是一个以计算机科学为基础&#xff0c;由计算机、心理学、哲学等多学科交叉融合的交叉学科、新兴学科。其研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学&#xff0c;企图了解智能的实质&#xff0c;并生产出一种新的能以人类…

【个人博客搭建】Hexo安装部署

目录 一、本地构建Hexo (一) 安装前提 1. Node.js 2. Git 3. Hexo (二) 初始化Hexo 1. 初始化博客目录 2. 配置网站基本信息 (三) 主题配置 1. 选择主题 2. 下载主题 (四) 本地启动Hexo 1. 生成静态文件 2. 启动服务 二、部署 (一) 部署到Github Pages 1. 新建…

Django实现富文本编辑器Ckeditor5图片上传功能

上一章我们已经为我们的博客继承了富文本编辑器Ckeditor5,虽然已经可以对文字进行排版处理,虽然已经可以通过插入图片的url地址来插入图片,但还无法通过本地上传图片,那么我们这个富文本编辑器就是不完整的,这一章我们将实现上传图片功能! ​ Ckeditor5图片上传采用的是…

[Python]窗体自动化解决方案之图形匹配

在图形目录下面命名想要点击的图形 生成Excel配置文件 数据检查 图形匹配(包含主任务) # 主任务从第i个开始执行j次 def ReCycle(i, j, file):# file "D:/AutoTest/PythonProject/UseCase/output.xlsx"test GUITest()wb xlrd.open_workbook(filenamefile)# 通过索…

·备忘录模式

备忘录模式 备忘录模式 备忘录模式 介绍&#xff1a;在不破坏封装的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态&#xff0c;这样可以在以后将对象恢复到原先的状态。 实现&#xff1a;备忘录类&#xff0c;有一个私有状态属性&#xf…

开源博客项目Blog .NET Core源码学习(8:EasyCaching使用浅析)

开源博客项目Blog使用EasyCaching模块实现缓存功能&#xff0c;主要是在App.Framwork项目中引用了多类包&#xff0c;包括内存缓存&#xff08;EasyCaching.InMemory&#xff09;、Redis缓存&#xff08;EasyCaching.CSRedis&#xff09;&#xff0c;同时支持多种序列化方式&am…

机器学习复习(4)——CNN算法

目录 数据增强方法 CNN图像分类数据集构建 导入数据集 定义trainer 超参数设置 数据增强 构建CNN网络 开始训练 模型测试 数据增强方法 # 一般情况下&#xff0c;我们不会在验证集和测试集上做数据扩增 # 我们只需要将图片裁剪成同样的大小并装换成Tensor就行 test_t…