掌握 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.rhkb.cn/news/248517.html

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

相关文章

Mysql流程控制函数

任何一门语言都有顺序结构 分支结构 循环结构 流程处理函数可以根据不同的条件&#xff0c;执行不同的处理流程&#xff0c;可以在 SQL 语句中实现不同的条件选择。 MySQL 中的流程处理函数主要包括 IF() 、 IFNULL() 和 CASE() 函数。 IF(value,value1,value2)函数举例 IF…

Qt无边框窗口拖拽和阴影

先看下效果&#xff1a; 说明 自定义窗口控件的无边框,窗口事件由于没有系统自带边框,无法实现拖拽拉伸等事件的处理,一种方法就是重新重写主窗口的鼠标事件&#xff0c;一种时通过nativeEvent事件处理。重写事件相对繁琐,我们这里推荐nativeEvent处理。注意后续我们在做win平…

微信小程序 仿微信聊天界面

1. 需求效果图 2. 方案 为实现这样的效果&#xff0c;首先要解决两个问题&#xff1a; 2.1.点击输入框弹出软键盘后&#xff0c;将已有的少许聊天内容弹出&#xff0c;导致看不到的问题 点击输入框弹出软键盘后&#xff0c;将已有的少许聊天内容弹出&#xff0c;导致看不到的问…

算法面试八股文『 基础知识篇 』

博客介绍 近期在准备算法面试&#xff0c;网上信息杂乱不规整&#xff0c;出于强迫症就自己整理了算法面试常出现的考题。独乐乐不如众乐乐&#xff0c;与其奖励自己&#xff0c;不如大家一起嗨。以下整理的内容可能有不足之处&#xff0c;欢迎大佬一起讨论。 PS&#xff1a;…

EPSON RC 机器人-第一个程序

创建项目 有机械人且用USB线连接好。可以USB。没有真机的选择 C4 Sample 可以运行程序。 否刚会提示【不能连接到控制器&#xff0c;未安装USB驱动器】 代码 按F5打开运行窗口 再点【开始】 点 【是】&#xff0c;查看运行结果

手把手教你使用Flask搭建ES搜索引擎(实战篇)

目录 一、引言 二、准备工作 三、搭建Flask应用程序 四、创建索引并插入数据 五、运行应用程序和测试搜索功能 一、引言 随着互联网的发展&#xff0c;搜索引擎已经成为我们获取信息的重要工具。然而&#xff0c;传统的搜索引擎如Google、Baidu等&#xff0c;虽然功能强大…

C语言之刷到的怪题(i与sizeof(i)比较大小)

这个题目一般都是选择输出<。为什么呢&#xff1f;因为i是一个全局变量&#xff0c;并且没有初始化&#xff0c;那么i的值就等于0。i--之后就是-1了。而sizeof(i)求出的就是整形变量对应的大小4个字节。-1<4&#xff0c;因此就选择 输出<。其实不然&#xff0c;这个si…

1.迭代与递归 - JS

迭代与递归是函数进阶的第一个门槛。迭代就是对已知变量反复赋值变换&#xff1b;递归就是函数体内调用自身。 迭代 一个迭代是就是一个循环&#xff0c;根据迭代式对变量反复赋值。 求近似根&#xff08;切线法&#xff09;&#xff1b; 迭代描述&#xff1a; x 0 x_0 x0…

mysql之基本查询

基本查询 一、SELECT 查询语句 一、SELECT 查询语句 本文所用案例已上传资源 查询所有列 1 SELECT *FORM emp;查询指定字段 SELECT empno,ename,job FROM emp;给字段取别名 SELECT empno 员工编号 FROM emp; SELECT empno 员工编号,ename 姓名,job 岗位 FROM emp; SELECT …

Servlet过滤器个监听器

过滤器和监听器 过滤器 什么是过滤器 当浏览器向服务器发送请求的时候&#xff0c;过滤器可以将请求拦截下来&#xff0c;完成一些特殊的功能&#xff0c;比如&#xff1a;编码设置、权限校验、日志记录等。 过滤器执行流程 Filter实例 package com.by.servlet;import jav…

Codeforces Round 799 (Div. 4)

目录 A. Marathon B. All Distinct C. Where’s the Bishop? D. The Clock E. Binary Deque F. 3SUM G. 2^Sort H. Gambling A. Marathon 直接模拟 void solve() {int ans0;for(int i1;i<4;i) {cin>>a[i];if(i>1&&a[i]>a[1]) ans;}cout<&l…

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

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

人脸识别技术在网络安全中有哪些应用前景?

人脸识别技术在网络安全中有广泛的应用前景。以下是一些主要的应用方向&#xff1a; 1. 身份验证和访问控制&#xff1a;人脸识别可以用作一种更安全和方便的身份验证方法。通过将用户的人脸与事先注册的人脸进行比对&#xff0c;可以实现强大的身份验证&#xff0c;避免了传统…

uniapp微信小程序-请求二次封装(直接可用)

一、请求封装优点 代码重用性&#xff1a;通过封装请求&#xff0c;你可以在整个项目中重用相同的请求逻辑。这样一来&#xff0c;如果 API 发生变化或者需要进行优化&#xff0c;你只需在一个地方修改代码&#xff0c;而不是在每个使用这个请求的地方都进行修改。 可维护性&a…

Pytest测试用例参数化

pytest.mark.parametrize(参数名1,参数名2...参数n, [(参数名1_data1,参数名2_data1...参数名n_data1),(参数名1_data2,参数名2_data2...参数名n_data2)]) 场景&#xff1a; 定义一个登录函数test_login,传入参数为name,password&#xff0c;需要用多个账号去测试登录功能 # …

DC-磁盘配额(23国赛真题)

2023全国职业院校技能大赛网络系统管理赛项–模块B&#xff1a;服务部署&#xff08;WindowServer2022&#xff09; 文章目录 DC-磁盘配额题目配置步骤验证查看DC2驱动器C:\的磁盘配额&#xff0c;限制磁盘空间&#xff0c;警告等级等配置 DC-磁盘配额 题目 在DC2驱动器C:\上…

Web3:B站chainlink课程Lesson5遇到的小坑汇总

ethers代码 我用的ethers.js 6 &#xff0c;和视频里一样用的是5的不用看代码部分 ethers.providers.JsonRpcProvider("server") //无了 ethers.JsonRpcProvider("server") //现在的wallet.getTransactionCount() //无了 wallet.getNonce() //现在的Big…

安卓主板_紫光展锐T820安卓主板方案定制

安卓主板采用了性能强劲的紫光展锐T820八核处理器&#xff0c;搭载了Android 13系统&#xff0c;为用户带来更加顺畅的操作体验。该主板不仅采用了6nm工艺&#xff0c;更加强大的算力和优越的性能&#xff0c;能够轻松实现多任务运行&#xff0c;不会出现卡顿现象。 此外&#…

C++初阶:入门泛型编程(函数模板和类模板)

大致介绍了一下C/C内存管理、new与delete后&#xff1a;C初阶&#xff1a;C/C内存管理、new与delete详解 我们接下来终于进入了模版的学习了&#xff0c;今天就先来入门泛型编程 文章目录 1.泛型编程2.函数模版2.1概念2.2格式2.3函数模版的原理2.4函数模版的实例化2.4.1隐式实例…

【论文阅读|小目标分割算法ASF-YOLO】

论文阅读|小目标分割算法ASF-YOLO 摘要&#xff08;Abstract&#xff09;1 引言&#xff08;Introduction&#xff09;2 相关工作&#xff08;Related work&#xff09;2.1 细胞实例分割&#xff08;Cell instance segmentation&#xff09;2.2 改进的YOLO用于实例分割&#xf…