Android JNI 技术入门指南
引言
在Android开发中,Java是一种主要的编程语言,然而,对于一些性能要求较高的场景(如音视频处理、图像处理、计算密集型任务等),我们可能需要使用到C或C++等语言来编写底层的高效代码。为了实现Java代码与C/C++代码之间的交互,Android提供了一个强大的工具——JNI(Java Native Interface)。通过JNI,Java可以调用C/C++代码,C/C++也可以调用Java代码,从而实现高效的原生交互。
开始之前先了解一些基础概念
开始之前
如果你对C/C++语言比较陌生,可以先看一下我的这两篇文章:
(大致过一下就行,挑重点去记,毕竟不是做C++开发,没必要完全理解,更多的是我们在开发中去学习)
- C语言基础
- C++基础
1. 什么是 JNI(Java Native Interface)?
JNI 是 Java 与其他编程语言(通常是 C 或 C++)之间的接口,允许 Java 代码与底层的本地代码进行交互。通过 JNI,我们可以在 Java 代码中调用本地(native)方法,或者让本地代码调用 Java 方法。
1.1 为什么要使用 JNI?
JNI 的主要作用是实现 Java 程序与本地程序之间的交互,特别是在以下几种情况下非常有用:
- 性能优化:有些运算或操作,Java 实现的效率可能较低,使用 C/C++ 可以提高性能,特别是在图像处理、音视频编解码等领域。
- 访问底层硬件或特性:Java 不能直接访问底层硬件或操作系统的某些特性,而 JNI 使得 Java 程序可以调用 C/C++ 中的底层代码,进而访问这些特性。
- 重用现有的本地代码库:有时为了节省开发时间,我们希望直接重用一些已有的 C/C++ 代码或第三方库,这时 JNI 就是连接 Java 和本地代码的桥梁。
1.2 JNI 如何工作?
JNI 的工作机制可以分为几个步骤:
- Java 调用 C/C++ 方法:通过在 Java 中声明本地方法(
native
),并使用System.loadLibrary()
加载本地库。Java 代码通过 JNI 机制调用底层的 C/C++ 函数。 - C/C++ 调用 Java 方法:JNI 允许在 C/C++ 中调用 Java 中的方法,甚至可以操作 Java 对象。
- 数据传递:通过 JNI,Java 和 C/C++ 之间可以传递基本数据类型(如整数、浮点数)和复杂的数据结构(如数组、对象等)。
1.3 JNI 的基本结构
- Java 层:Java 中声明
native
方法,并通过System.loadLibrary()
加载本地库。 - 本地层:通过 C/C++ 实现 JNI 接口,并将它编译成共享库(.so 文件)。
- JNI 头文件:使用
javah
工具(或者在 Android 中通过ndk-build
)生成的头文件,定义了 Java 类与本地方法之间的映射关系。
2. NDK 与 JNI 的关系
在 Android 开发中,NDK(Native Development Kit)是一个工具集,它允许开发者在 Android 应用中编写和使用 C/C++ 代码。JNI 是 NDK 的一部分,它提供了 Android 中 Java 代码和 C/C++ 本地代码之间的交互接口。
2.1 NDK 的功能
NDK 是一组工具和库,允许开发者用 C 和 C++ 编写 Android 应用中的一些性能关键的代码。NDK 提供的功能包括:
- 访问硬件资源:通过 NDK,你可以直接访问一些低级的硬件特性,比如摄像头、传感器、GPS 等。
- 性能优化:一些计算密集型的任务(例如图像处理、音视频编解码等)可以通过 C/C++ 实现,性能上更有优势。
- 使用已有的本地库:有时候开发者会利用一些已有的 C/C++ 库或第三方库,而这些库通常需要通过 NDK 来编译和链接。
2.2 NDK 与 JNI 的结合
- JNI 是 NDK 与 Java 层之间的桥梁,利用 JNI,Java 层可以调用本地层的 C/C++ 函数,反之,C/C++ 代码也可以调用 Java 层的代码。
- 使用 NDK 时,JNI 使得 Java 和 C/C++ 之间的数据和方法调用变得可能。
- 通过 JNI,我们可以在 Java 代码中调用 NDK 中编写的本地方法,或者直接操作 Java 对象。
3. 数据类型
Java、JNI、C/C++ 三者之间的数据类型转换是跨语言编程中的一个核心问题,尤其在涉及到 Java 调用 C/C++ 编写的本地方法时。JNI(Java Native Interface)作为 Java 与 C/C++ 交互的桥梁,提供了一套标准机制来实现 Java 与本地代码之间的数据交换。
3.1 基础类型
Java 通过 JNI 与 C/C++ 交互时,JNI 提供了一些专门的类型和方法来桥接 Java 类型与 C/C++ 类型的差异。
Java 类型 | JNI 类型 | C/C++ 类型 | 备注 |
---|---|---|---|
byte | jbyte | char (8-bit) | JNI 使用 jbyte 来表示 Java 的 byte 类型。 |
short | jshort | short (16-bit) | JNI 使用 jshort 来表示 Java 的 short 类型。 |
int | jint | int (32-bit) | JNI 使用 jint 来表示 Java 的 int 类型。 |
long | jlong | long long (64-bit) | JNI 使用 jlong 来表示 Java 的 long 类型。 |
float | jfloat | float (32-bit) | JNI 使用 jfloat 来表示 Java 的 float 类型。 |
double | jdouble | double (64-bit) | JNI 使用 jdouble 来表示 Java 的 double 类型。 |
char | jchar | wchar_t (16-bit) | JNI 使用 jchar 来表示 Java 的 char 类型,它是 16 位 Unicode 字符,C/C++ 中通常用 wchar_t 来表示宽字符。 |
boolean | jboolean | bool (1-bit) | JNI 使用 jboolean 来表示 Java 的 boolean 类型,jboolean 是 8 位的布尔值,通常与 C/C++ 中的 bool 类型兼容。 |
3.2 引用类型
Java 对象类型通常通过 JNI 提供的 API 转换为 C/C++ 中的指针类型,这些指针类型并不代表实际的数据内容,而是用于访问 Java 对象或方法的接口。
Java 类型 | JNI 类型 | C/C++ 类型 | 转换方式 | JNI API 示例 |
---|---|---|---|---|
String | jstring | jstring | Java String 到 C/C++ 的转换(通过 GetStringUTFChars 或 GetStringChars ) | env->GetStringUTFChars(jstring, nullptr) |
Object | jobject | jobject | Java 对象到 C/C++ 的转换,可以用来操作任意 Java 对象 | env->GetObjectClass(jobject) |
Class | jclass | jclass | Java Class 对象到 C/C++ 的转换,通过 FindClass 或 GetObjectClass 获取类引用 | env->FindClass("java/lang/String") |
Array (Object) | jobjectArray | jobjectArray | 对象数组到 C/C++ 的转换,通过 JNI API 访问数组元素 | env->GetObjectArrayElement(jobjectArray, index) |
Array (Primitive) | jintArray | jintArray | 基本类型数组转换(如 int[] 到 jintArray ) | env->GetIntArrayElements(jintArray, nullptr) |
Field | jfieldID | jfieldID | 通过 JNI 获取字段 ID,通常用于访问 Java 类中的字段 | env->GetFieldID(jclass, "fieldName", "I") |
Method | jmethodID | jmethodID | 通过 JNI 获取方法 ID,通常用于调用 Java 方法 | env->GetMethodID(jclass, "methodName", "()V") |
4. JNI 中的 Java 签名信息
在学习签名之前,先来看一段Java反射代码:
import java.lang.reflect.Method;public class ReflectionExample {public void sayHello(String name) {System.out.println("Hello, " + name);}public static void main(String[] args) throws Exception {// 获取 ReflectionExample 类的 Class 对象Class<?> clazz = Class.forName("ReflectionExample");// 获取方法 sayHello(String)Method method = clazz.getMethod("sayHello", String.class);// 创建实例并调用方法Object instance = clazz.getDeclaredConstructor().newInstance();method.invoke(instance, "World");}
}
在clazz.getMethod
中,我们通过方法名称 和 参数类型拿到了sayHello
方法,在JNI中C/C++ 调用Java的方法也类似,不同点是参数类型 和 返回值 要用签名方式代替(因为C/C++不能直接拿到Java方法嘛),那么JNI中签名长什么样呢?
4.1 基本数据类型的签名
Java 中的基本数据类型对应 JNI 中的签名符号。JNI 使用单一字符来表示 Java 中的基本数据类型。
Java 类型 | JNI 签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
4.2 对象类型的签名
Java 对象类型(类类型、接口类型等)的签名格式如下:
- 以
L
开始,后接类的全名(包括包名),最后以;
结尾。例如,String
类型的签名为Ljava/lang/String;
。 - 注意:数组类型的签名也以
[
开头,并且每增加一个维度就多一个[
。
Java 类型 | JNI 签名 |
---|---|
String | Ljava/lang/String; |
Object | Ljava/lang/Object; |
int[] | [I |
String[] | [Ljava/lang/String; |
Object[] | [Ljava/lang/Object; |
4.3 方法签名
Java 方法的签名由两部分组成:方法的参数类型和返回类型,方法签名的格式为:(参数类型1, 参数类型2, ...)返回类型
。例如,一个有两个 int
参数并返回 String
类型的方法签名为 (II)Ljava/lang/String;
。
Java 方法 | JNI 签名 |
---|---|
int add(int a, int b) | (II)I |
String getName(String name) | (Ljava/lang/String;)Ljava/lang/String; |
void setValues(int x, int y) | (II)V |
4.4 构造函数签名
Java 构造函数的签名与普通方法类似,不同之处在于构造函数没有返回类型(V
),且通常没有方法名。在 JNI 中,构造函数的签名格式是 (参数类型1, 参数类型2, ...)V
。
Java 构造函数 | JNI 签名 |
---|---|
MyClass(int, String) | (ILjava/lang/String;)V |
4.5 静态方法签名(重点)
静态方法的签名与实例方法类似,唯一的区别是静态方法是类级别的,因此它通过类的对象引用来调用。静态方法的签名与实例方法的签名相同,但 JNI 调用时不需要实例对象。
没必要死记硬背,有规律的,写两遍就记住了
4.6 示例
(1) 获取 Java 方法签名
GetMethodID
或 GetStaticMethodID
,拿到相应的方法。
jmethodID methodId = env->GetMethodID(clazz, "methodName", "(I)Ljava/lang/String;");
这个方法的签名为 (I)Ljava/lang/String;,表示该方法有一个 int 类型的参数,返回一个 String 类型。
(2) 获取字段签名
GetFieldID
或 GetStaticFieldID
,拿到类的属性字段。
jfieldID fieldId = env->GetFieldID(clazz, "fieldName", "Ljava/lang/String;");
这个字段的签名为 Ljava/lang/String;,表示它是一个 String 类型的字段。
(3) 构造函数签名
通过签名和构造函数名称查找类的构造函数 ID。构造函数的签名与普通方法相同,但没有返回类型。
jmethodID constructorId = env->GetMethodID(clazz, "<init>", "(I)V");
构造函数的签名为 (I)V,表示它接受一个 int 类型的参数并没有返回值。
5. 在Android中使用JNI
5.1 配置项目
在build.gradle
包含对NDK的支持:
android {...defaultConfig {...externalNativeBuild {cmake {cppFlags ""}}}externalNativeBuild {cmake {path "CMakeLists.txt"}}
}
5.2 编写Java代码
在Java代码中声明本地方法:
public class NativeLib {static {System.loadLibrary("native-lib");}public native String stringFromJNI();
}
5.3 编写C/C++代码
在cpp目录下创建对应的C/C++文件,实现上述声明的本地方法:
#include <jni.h>
#include <string>extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_NativeLib_stringFromJNI(JNIEnv* env, jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}
5.4 配置CMakeLists.txt
在项目的根目录下,配置CMakeLists.txt 如:
cmake_minimum_required(VERSION 3.4.1)add_library(native-libSHAREDsrc/main/cpp/native-lib.cpp)find_library(log-liblog)target_link_libraries(native-lib${log-lib})
如果你项目中想写多个.cpp
文件,CMakeLists.txt xiugai配置如下:
cmake_minimum_required(VERSION 3.4.1)add_library(native-libSHAREDsrc/main/cpp/native-lib.cpp)add_library(native-lib2SHAREDsrc/main/cpp/native-lib2.cpp)//更多...find_library(log-liblog)target_link_libraries(native-lib${log-lib})target_link_libraries(native-lib2${log-lib})//更多...
即在 find_library
和 target_link_libraries
增加相对应的.cpp
文件即可。
6. 实战
因为在写这篇文章之前,我已经完善了一些实战的功能,在此就不一一讲解了,包括:
- 传递int数据
- 传递String数据
- 传递Array数据
- 在C++中调用Java的返回值Void方法
- 在C++中调用Java的返回值int方法
- 在C++中调用Java的返回值String方法
- 在C++中显示Toast
- 文本加解密演示
- 锅炉压力进度条
- C++ 创建子线程
- C++ 线程锁之生产者消费者
- 串口通信(SerialPort) - 可拿来直接使用,已验证功能。
代码已经上传Github:JNIStudy,感兴趣的可以下载看看,里面我加了世上最全注释,由基础到复杂,看不懂来打我!😆
打包为.so
文件可以看我的这篇文章:在Android中,将 .cpp 文件编译成共享库(.so 文件)
7. 最后
之前一直对JNI望而却步,真正学过后回头看看,也不是那么的难,难的是你不主动去学。所有伟大,都源于一个勇敢的开始!共勉!
另外给喜欢记笔记的同学安利一款好用的云笔记软件,对比大部分国内的这个算还不错的,免费好用:wolai
相关文章:

Android JNI 技术入门指南
引言 在Android开发中,Java是一种主要的编程语言,然而,对于一些性能要求较高的场景(如音视频处理、图像处理、计算密集型任务等),我们可能需要使用到C或C等语言来编写底层的高效代码。为了实现Java代码与C…...

实在智能受邀出席柳州市智能终端及机器人产业发展合作大会
10 月 27 日至 28 日,由中共柳州市委员会与柳州市人民政府主办的2024柳州市智能终端及机器人产业发展合作大会在柳州莲花山庄隆重举行。大会充分整合各方资源,持续深化与柳州在重大战略规划、重大平台建设、重点产业培育等领域的合作。作为智能体行业的知…...

算法求解(C#)-- 寻找包含目标字符串的最短子串算法
1. 引言 在字符串处理中,我们经常需要从一个较长的字符串中找到包含特定目标字符串的最短子串。这个问题在文本搜索、基因序列分析等领域有着广泛的应用。本文将介绍一种高效的算法来解决这个问题。 2. 问题描述 给定一个源字符串 source 和一个目标字符串 targe…...

AscendC从入门到精通系列(二)基于Kernel直调开发AscendC算子
本次主要讨论下AscendC算子的开发流程,基于Kernel直调工程的算子开发。 1 AscendC算子开发的基本流程 使用Ascend C完成Add算子核函数开发; 使用ICPU_RUN_KF CPU调测宏完成算子核函数CPU侧运行验证; 使用<<<>>>内核调用符…...

DAO模式的理解
目录 DAO模式 含义 DAO模式 的理解 分层思维 分层含义 分层目的 dao层 dao包(对接的是操作数据库的接口) dao包下lmpl 包(dao包中接口的实现类) 补充 1 你创建的实体类需要和数据库中建的表一一对应。 总结 DAO模式 含义…...

使用GitHub Actions实现CI/CD流程
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 使用GitHub Actions实现CI/CD流程 GitHub Actions 简介 创建仓库 配置工作流 示例工作流文件 触发和运行工作流 部署应用 最佳实…...

机器人助力Bridge Champ游戏:1.4.2版本如何提升玩家体验
在Bridge Champ游戏中,机器人扮演着桥牌游戏的“无名英雄”角色,默默地提升玩家体验。凭借智能化的设计,这些机器人不仅能够陪练,也大大提升了比赛的流畅度与趣味性。 Bridge Champ是什么 Bridge Champ是一个基于Ignis公链的在线…...

滑动窗口(单调队列维护窗口)-acwing
题目: 154. 滑动窗口 - AcWing题库 代码(删除队列窗口多余的>单调队列) 判断最值是否滑出窗口可以放在 入队的后面。 但是,判断,准备入队元素比前面小,要从队尾出队,放在入队前。 总之&a…...

ALB搭建
ALB: 多级分发、消除单点故障提升应用系统的可用性(健康检查)。 海量微服务间的高效API通信。 自带DDoS防护,集成Web应用防火墙 配置: 1.创建ECS实例 2.搭建应用 此处安装的LNMP 3.创建应用型负载均衡ALB实例 需要创建服务关联角…...

c# 动态lambda实现二级过滤(支持多种参数类型和模糊查询)
效果 调用方法 实体类(可以根据需求更换) public class ToolStr50 {public bool isSelected { get; set; }public string toolStr1 { get; set; }public string toolStr2 { get; set; }public string toolStr3 { get; set; }public string toolStr4 { …...

第J5周:DenseNet+SE-Net实战
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 任务: ●1. 在DenseNet系列算法中插入SE-Net通道注意力机制,并完成猴痘病识别 ●2. 改进思路是否可以迁移到其他地方呢 ●3. 测试集acc…...

Intern大模型训练营(五):书生大模型全链路开源体系笔记
观看视频,可以比较详细地了解到书生大模型全链路开源体系。 其中有几个印象比较深的点: 这张图讲述了书生浦语大模型开源的发展史,同时与主流的llama和Chatgpt模型进行比较,可以看出在参数上,InterLM在努力追赶甚至超…...

聚观早报 | 比亚迪腾势D9登陆泰国;苹果 iOS 18.2 将发布
聚观早报每日整理最值得关注的行业重点事件,帮助大家及时了解最新行业动态,每日读报,就读聚观365资讯简报。 整理丨Cutie 11月5日消息 比亚迪腾势D9登陆泰国 苹果 iOS 18.2 将发布 真我GT7 Pro防尘防水细节 小米15 Ultra最快明年登场 …...

微信小程序开发,诗词鉴赏app,诗词搜索实现(三)
微信小程序开发,诗词鉴赏app(一): https://blog.csdn.net/jky_yihuangxing/article/details/143501681微信小程序开发,诗词鉴赏app,诗词推荐实现(二):https://blog.csdn.net/jky_yih…...

Kotlin 协程使用及其详解
Kotlin协程,好用,但是上限挺高的,我一直感觉自己就处于会用,知其然不知其所以然的地步。 做点小总结,比较浅显。后面自己再继续补充吧。 一、什么是协程? Kotlin 协程是一种轻量级的并发编程方式&#x…...

计算机组成原理--三章四章
这里写目录标题 第三章:存储系统3.1 存储系统基本概念引入存储器的层次结构简介产品 存储器的分类按层次分类按照介质分类按照存取方式分类按照信息的可更改性按照信息的可保护性 存储器的性能指标存储容量单位成本存储速度 总结 3.2主存储器的基本组成半导体元器件…...

单片机工程使用链接优化-flto找不到定义_链接静态库
IDE: CLion HOST: Windows 11 MinGW:x86_64-14.2.0-release-posix-seh-ucrt-rt_v12-rev0 GCC: arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi 示例工程:https://github.com/ichliebedich-DaCapo/STM…...

UniTask/Unity的PlayerLoopTiming触发顺序
开始尝试在项目中使用UniTask,发现其中的UniTask.Yield确实很好用,还可以传入PlayerLoopTiming来更细致的调整代码时机,不过平常在Mono中接触的只有Awake,Start,Update等常用Timing,其他的就没怎么接触了&a…...

【报错记录】Steam迁移(移动)游戏报:移动以下应用的内容失败:XXX: 磁盘写入错误
前言 由于黑神话悟空,导致我的2TB的SSD系统盘快满了,我又买了一块4TB的SSD用来存放游戏,我就打算把之前C盘里的游戏移动到D盘,结果Steam移动游戏居然报错了,报的还是“磁盘写入错误”,如下图所示ÿ…...

C 语言学习-04【结构化程序设计】
1、单分支结构语句 用单分支结构进行奇偶判断: #include <stdio.h>int main() {int num;printf("Please enter an integer: ");scanf("%d", &num);if (num % 2 ! 0) {printf("%d is odd! \n", num);}if (num % 2 0) {prin…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...

抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...

JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!
今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等,设置经线、纬线都以10间隔显示。 2、需要插入背会归线…...