掌握 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…

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

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

【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. 新建…

开源博客项目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…

【国产MCU】-CH32V307-GPIO控制:输入与输出

GPIO控制:输入与输出 文章目录 GPIO控制:输入与输出1、GPIO简单介绍2、驱动API介绍3、GPIO配置代码实现3.1 GPIO配置为输出3.2 GPIO配置为输入CH32V307的GPIO口可以配置成多种输入或输出模式,内置可关闭的上拉或下拉电阻,可以配置成推挽或开漏功能。GPIO口还可以复用成其他…

一文掌握SpringBoot注解之@Component 知识文集(8)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

人工智能(pytorch)搭建模型23-pytorch搭建生成对抗网络(GAN):手写数字生成的项目应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(pytorch)搭建模型23-pytorch搭建生成对抗网络(GAN):手写数字生成的项目应用。生成对抗网络&#xff08;GAN&#xff09;是一种强大的生成模型&#xff0c;在手写数字生成方面具有广泛的应用前景。通过生成…

类和对象 第六部分 继承 第一部分:继承的语法

一.继承的概念 继承是面向对象的三大特性之一 有些类与类之间存在特殊的关系&#xff0c;例如下图&#xff1a; 我们可以发现&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性&#xff0c;这个时候&#xff0c;我们可以讨论利用继承的技术&#xff0c;…

【前端素材】bootstrap3 实现地产置业公司source网页设计

一、需求分析 地产置业公司的网页通常是该公司的官方网站&#xff0c;旨在向访问者提供相关信息和服务。这些网页通常具有以下功能&#xff1a; 公司介绍&#xff1a;网页通常包含有关公司背景、历史、核心价值观和使命等方面的信息。此部分帮助访问者了解公司的身份和目标。 …

LabVIEW船舶自动识别系统

在现代航海领域&#xff0c;安全高效的船舶自动识别系统对于保障航行安全和提高船舶管理效率非常重要。介绍了利用LabVIEW软件开发的一个船舶自动识别系统&#xff0c;该系统通过先进的数据采集和信号处理技术&#xff0c;显著提升了传统自动识别系统的性能。 这个船舶自动识别…