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 的工作机制可以分为几个步骤:

  1. Java 调用 C/C++ 方法:通过在 Java 中声明本地方法(native),并使用 System.loadLibrary() 加载本地库。Java 代码通过 JNI 机制调用底层的 C/C++ 函数。
  2. C/C++ 调用 Java 方法:JNI 允许在 C/C++ 中调用 Java 中的方法,甚至可以操作 Java 对象。
  3. 数据传递:通过 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++ 类型备注
bytejbytechar (8-bit)JNI 使用 jbyte 来表示 Java 的 byte 类型。
shortjshortshort (16-bit)JNI 使用 jshort 来表示 Java 的 short 类型。
intjintint (32-bit)JNI 使用 jint 来表示 Java 的 int 类型。
longjlonglong long (64-bit)JNI 使用 jlong 来表示 Java 的 long 类型。
floatjfloatfloat (32-bit)JNI 使用 jfloat 来表示 Java 的 float 类型。
doublejdoubledouble (64-bit)JNI 使用 jdouble 来表示 Java 的 double 类型。
charjcharwchar_t (16-bit)JNI 使用 jchar 来表示 Java 的 char 类型,它是 16 位 Unicode 字符,C/C++ 中通常用 wchar_t 来表示宽字符。
booleanjbooleanbool (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 示例
StringjstringjstringJava String 到 C/C++ 的转换(通过 GetStringUTFCharsGetStringCharsenv->GetStringUTFChars(jstring, nullptr)
ObjectjobjectjobjectJava 对象到 C/C++ 的转换,可以用来操作任意 Java 对象env->GetObjectClass(jobject)
ClassjclassjclassJava Class 对象到 C/C++ 的转换,通过 FindClassGetObjectClass 获取类引用env->FindClass("java/lang/String")
Array (Object)jobjectArrayjobjectArray对象数组到 C/C++ 的转换,通过 JNI API 访问数组元素env->GetObjectArrayElement(jobjectArray, index)
Array (Primitive)jintArrayjintArray基本类型数组转换(如 int[]jintArrayenv->GetIntArrayElements(jintArray, nullptr)
FieldjfieldIDjfieldID通过 JNI 获取字段 ID,通常用于访问 Java 类中的字段env->GetFieldID(jclass, "fieldName", "I")
MethodjmethodIDjmethodID通过 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 签名
booleanZ
byteB
charC
shortS
intI
longJ
floatF
doubleD
voidV

4.2 对象类型的签名

Java 对象类型(类类型、接口类型等)的签名格式如下:

  • L 开始,后接类的全名(包括包名),最后以 ; 结尾。例如,String 类型的签名为 Ljava/lang/String;
  • 注意:数组类型的签名也以 [ 开头,并且每增加一个维度就多一个 [
Java 类型JNI 签名
StringLjava/lang/String;
ObjectLjava/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 方法签名

GetMethodIDGetStaticMethodID,拿到相应的方法。

jmethodID methodId = env->GetMethodID(clazz, "methodName", "(I)Ljava/lang/String;");

这个方法的签名为 (I)Ljava/lang/String;,表示该方法有一个 int 类型的参数,返回一个 String 类型。

(2) 获取字段签名
GetFieldIDGetStaticFieldID,拿到类的属性字段。

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_librarytarget_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

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

供应商srm管理,招投标管理,电子采购管理,在线询价,在线报价,供应商准入审核(java代码)

前言&#xff1a; 随着互联网和数字技术的不断发展&#xff0c;企业采购管理逐渐走向数字化和智能化。数字化采购平台作为企业采购管理的新模式&#xff0c;能够提高采购效率、降低采购成本、优化供应商合作效率&#xff0c;已成为企业实现效益提升的关键手段。系统获取在文末…

Java 函数接口Supplier【供给型接口】简介与示例

Java中四个重要的函数式接口&#xff1a;Function、Predicate、Consumer和Supplier。这些接口是函数式编程的基础&#xff0c;Function用于转换操作&#xff0c;Predicate用于进行条件判断&#xff0c;Consumer用于消费输入而不产生输出&#xff0c;而Supplier则用于提供值但不…

线程与进程的区别(面试)

一.进程 进程&#xff1a;一个程序启动起来&#xff0c;就会对应一个进程&#xff0c;进程就是系统分配资源的基本单位。 上面一部分进程是我们自己去执行应用的可执行文件, 而另一部分是操作系统自动启动的进程. 二.线程 线程&#xff1a;线程是进程中的一个执行单元&#xff…

VMware调整窗口为可以缩小但不改变显示内容的大小

也就是缩小窗口不会影响内容的大小 这样设置就好

OpenAI 发布了新的事实性基准——SimpleQA

SimpleQA 简介 名为 SimpleQA 的事实性基准&#xff0c;用于衡量语言模型回答简短的事实性问题的能力。 人工智能领域的一个悬而未决的问题是如何训练模型&#xff0c;使其产生符合事实的回答。 目前的语言模型有时会产生错误的输出或没有证据证明的答案&#xff0c;这个问题…

酒店民宿小程序,探索行业数字化管理发展

在数字化发展时代&#xff0c;各行各业都开始向数字化转型发展&#xff0c;酒店民宿作为热门行业也逐渐趋向数字、智能化发展。 对于酒店民宿来说&#xff0c;如何将酒店特色服务优势等更加快速运营推广是重中之重。酒店民宿小程序作为一款集结预约、房源管理、客户订单管理等…

[C++11] 可变参数模板

文章目录 基本语法及原理可变参数模板的基本语法参数包的两种类型可变参数模板的定义 sizeof... 运算符可变参数模板的实例化原理可变参数模板的意义 包扩展包扩展的基本概念包扩展的实现原理编译器如何展开参数包包扩展的高级应用 emplace 系列接口emplace_back 和 emplace 的…

使用Ubuntu快速部署MinIO对象存储

想拥有自己的私有云存储&#xff0c;安全可靠又高效&#xff1f;MinIO是你的理想选择&#xff01;这篇文章将手把手教你如何在Ubuntu 22.04服务器上部署MinIO&#xff0c;并使用Nginx反向代理和Let’s Encrypt证书进行安全加固。 即使你是新手&#xff0c;也能轻松完成&#xf…

贝尔不等式,路径积分与AB(Aharonov-Bohm)效应

贝尔不等式、路径积分与Aharonov-Bohm&#xff08;AB&#xff09;效应 这些概念分别源于量子力学不同的理论分支和思想实验&#xff0c;但它们都揭示了量子力学的奇异性质&#xff0c;包括非局域性、相位效应和波粒二象性。以下详细解析每一概念&#xff0c;并探讨其相互联系。…

用友U8接口-isHasCounterSignPiid错误

错误消息 调用U813的审批流方法报错&#xff0c;找不到方法:“Boolean UFIDA.U8.Audit.BusinessService.ManualAudit.isHasCounterSignPiid System.Web.Services.Protocols.SoapException:服务器无法处理请求。 ---> System.MissingMethodException: 找不到方法:“Boolean…

QJson-趟过的各种坑(先坑后用法)

QJson-趟过的各种坑【先坑后用法】 Chapter1 QJson-趟过的各种坑【先坑后用法】一、不能处理大数据量&#xff0c;如果你的数据量有百兆左右(特别是有的小伙伴还喜欢json格式化输出的)&#xff0c;不要用Qjson&#xff0c;否则会报错 DocumentTooLarge二、json格式化输出1.构建…

flink实战-- flink任务的火焰图如何使用

火焰图 Flame Graphs 是一种有效的可视化工具,可以帮助我们排查如下问题: 目前哪些方法正在消耗 CPU 资源?一个方法的消耗与其他方法相比如何?哪一系列的堆栈调用导致了特定方法的执行?y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的…

.Net Core 6.0 WebApi在Centos中部署

查看已经开发的端口的列表 firewall-cmd --zonepublic --list-ports .net core sdk密匙 sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm sudo yum update .net core sdk安装 sudo yum install -y dotnet-sdk-6.0 sudo dnf in…

Java基于SpringBoot+Vue的农产品电商平台

大家好&#xff0c;我是Java徐师兄&#xff0c;今天为大家带来的是Java基于SpringBoot 的农产品电商平台。该系统采用 Java 语言 开发&#xff0c;MySql 作为数据库&#xff0c;系统功能完善 &#xff0c;实用性强 &#xff0c;可供大学生实战项目参考使用。 博主介绍&#xff…

一文读懂系列:结合抓包分析,详解SSH协议通信原理

SSH协议通过建立加密通道来提供安全的远程访问、文件传输和执行远程命令等操作。接下来我们就通过具体示例和抓包分析&#xff0c;让大家清楚地了解SSH协议的神秘面纱&#xff01;如有更多疑问&#xff0c;欢迎讨论区留言讨论~ 1. SSH简介 SSH&#xff08;Secure Shell&#x…

数据冒险-ld和add(又称load-use冒险)

第一张图没有使用前递&#xff0c;第二张图使用前递&#xff0c;chatgpt分析第二张图 这张图展示了一个流水线的执行过程&#xff0c;其中存在读后写&#xff08;RAW&#xff09;数据冒险。我们可以通过**前递&#xff08;Forwarding&#xff09;**技术来解决这个数据冒险&…

Java 的 Scanner 类:控制台输入与文件扫描

Java 的 Scanner 类是一个非常方便的工具类&#xff0c;主要用于从控制台或文件中扫描输入数据。虽然它也可以用于扫描文件内容&#xff0c;但我们通常更喜欢它用于控制台输入&#xff0c;因为扫描文件可以通过文件流来完成。接下来&#xff0c;我们将通过几个简单的示例来讲解…

安卓市场如何做APP的分发、推广?

今天主要跟大家分享一些分发、推广这块操作的内容以及对安卓用户的一些理解。 分发的日常生活&#xff1a;“某渠道怎样怎样&#xff0c;应用宝是不是要加点预算&#xff0c;OPPO是不是要加点预算&#xff0c;你的成本又高了&#xff0c;华为又掉注册&#xff0c;应用宝又掉注册…

基于JavaWeb的图书售卖网站(源码+部署+LW)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于JavaWeb的图书售卖网…

高级 <HarmonyOS主题课>构建华为支付服务的课后习题

五色令人目盲&#xff1b; 五音令人耳聋&#xff1b; 五味令人口爽&#xff1b; 驰骋畋猎&#xff0c;令人心发狂&#xff1b; 难得之货&#xff0c;令人行妨&#xff1b; 是以圣人为腹不为目&#xff0c;故去彼取此。 本篇内容主要来自&#xff1a;<HarmonyOS主题课>构建…