Android JNI 技术入门指南

news2024/11/24 23:02:09

在这里插入图片描述

引言

在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-lib
    SHARED
    src/main/cpp/native-lib.cpp)

find_library(
    log-lib
    log)

target_link_libraries(
    native-lib
    ${log-lib})

如果你项目中想写多个.cpp文件,CMakeLists.txt xiugai配置如下:

cmake_minimum_required(VERSION 3.4.1)

add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp)

add_library(
    native-lib2
    SHARED
    src/main/cpp/native-lib2.cpp)

//更多...

find_library(
    log-lib
    log)

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.coloradmin.cn/o/2236849.html

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

相关文章

设计模式之抽象工厂模式(替换Redis双集群升级,代理类抽象场景)

前言&#xff1a; 看了很多书、学了很多知识&#xff0c;多线程能玩出花&#xff0c;可最后我还是写不好代码&#xff01; 这就有点像家里装修完了买物件&#xff0c;我几十万的实木沙发&#xff0c;怎么放这里就不好看。同样代码写的不好并不一定是基础技术不足&#xff0c;也…

LeetCode 热题 100之 堆

1.数组中第k个最大元素 和Acwing 786 第k个数一模一样 排序 思路分析1&#xff1a;此题要求时间复杂度未为O(n)。虽然库函数sort和快速排序都能过&#xff0c;但是时间复杂度不满足条件。下面优化快速排序&#xff0c;写一个快速选择算法。我们可以引入随机化来加速这个过程&…

java-智能识别车牌号_基于spring ai和开源国产大模型_qwen vl

用大模型做车牌号识别&#xff0c;最简单高效 在Java场景中&#xff0c;java识别车牌号的需求非常普遍。过去&#xff0c;我们主要依赖OCR等传统方法来实现java识别车牌号&#xff0c;但这些方法的效果往往不稳定。随着技术的发展&#xff0c;现在有了更先进的解决方案——大模…

java ssm 网上蛋糕店 在线蛋糕甜品管理 网上蛋糕管理 源码 jsp

一、项目简介 本项目是一套基于SSM的网上蛋糕店&#xff0c;主要针对计算机相关专业的和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本、软件工具等。 项目都经过严格调试&#xff0c;确保可以运行&#xff01; 二、技术实现 ​后端技术&#xff1a;S…

Qt编译lua库并调用

参考博客&#xff1a; 编译lua库 参考下面文章编译lua库文件 QT5.9学习笔记之QT编译lua库_qtluaintf.h-CSDN博客 https://blog.csdn.net/qq_23345187/article/details/112710677 Qt代码引用lua库文件 打开pro项目文件&#xff0c;右键空白处&#xff0c;点击添加库&#xff…

shopify模块新增内容或图片

1、后台找到指定的liquid页面&#xff0c;在该页面下方{% schema %} 新增需求 2、添加轮播图功能 {% comment %} 轮播代码 {% endcomment %}{% if block.settings.enable_slider %}<divclass"size-guide-slider swiper"data-slides-per-view"{{ block.setti…

【Ant Design Pro】不想用轻量的hook就喜欢用dva的数据状态管理

就像TS是JS的超集一样&#xff0c;antdpro框架也类似&#xff0c;底层也是用dva来构建的。关于数据管理&#xff0c;官方还是建议我们使用轻量的hooks方法来进行操作使用。 使用dva实现数据状态管理效果 框架中的数据管理模式 简单的数据共享 对于简单的应用&#xff0c;不需…

基于 CMSIS-PACK 移植Bootloader

基于 CMSIS-PACK 移植 1.准备工作 准备一份基础的裸机源码 (可通过 STM32CubeMx 可视化软件创建也可按照工程项目所需文档手动创建) 工程&#xff0c;如一份 stm32 包含一个支持 printf 的串口初始化代码。 2.安装Pack包 在 MDK 中部署 **MicroBoot **的第一步是获取对应的…

使用ChatGPT神速精读文献,12个高阶ChatGPT提示词指令,值得你复制使用

在学术研究的道路上,文献的阅读和分析往往是我们迈向深层次理解的第一步。如何有效提取文献中的核心要点,如何全面总结一个研究的背景与贡献,甚至如何深入剖析论文中的每个细节,都是每个研究者必须掌握的技能。通过系统化的文献分析,我们不仅能了解现有研究的框架与成果,…

PICO+Unity MR空间锚点

官方链接&#xff1a;空间锚点 | PICO 开发者平台 注意&#xff1a;该功能只能打包成APK在PICO 4 Ultra上真机运行&#xff0c;无法通过串流或PICO developer center在PC上运行。使用之前要开启视频透视。 在 Inspector 窗口中的 PXR_Manager (Script) 面板上&#xff0c;勾选…

Python Matplotlib 如何绘制股票或金融数据图

Python Matplotlib 如何绘制股票或金融数据图 在金融领域&#xff0c;数据可视化是分析市场趋势、股票表现和财务健康的重要工具。Python 的 Matplotlib 库为我们提供了强大的功能来绘制股票和金融数据图。本文将详细介绍如何使用 Matplotlib 绘制这些图表&#xff0c;并且结合…

Golang--反射

1、概念 反射可以做什么? 反射可以在运行时动态获取变量的各种信息&#xff0c;比如变量的类型&#xff0c;类别等信息如果是结构体变量&#xff0c;还可以获取到结构体本身的信息(包括结构体的字段、方法)通过反射&#xff0c;可以修改变量的值&#xff0c;可以调用关联的方法…

【Web前端】使用 JSON 处理数据

JSON 是一种基于 JavaScript 对象语法的数据格式&#xff0c;由道格拉斯克罗克福特推广。尽管其语法源于 JavaScript&#xff0c;JSON 仍然是独立于 JavaScript 的&#xff0c;这也是为什么许多编程环境能够解析和生成 JSON 的原因。JSON 可以以对象或字符串的形式存在&#xf…

VMware 虚拟机使用教程及 Kali Linux 安装指南

VMware 虚拟机使用教程及 Kali Linux 安装指南 在现代计算机科学与网络安全领域&#xff0c;虚拟化技术的应用越来越广泛。VMware 是一款功能强大的虚拟化软件&#xff0c;可以帮助用户在同一台物理机上运行多个操作系统。本文将详细介绍如何使用 VMware 虚拟机&#xff0c;并…

达梦8数据库适配ORACLE的8个参数

目录 1、概述 1.1 概述 1.2 实验环境 2、参数简介 3、实验部分 3.1 参数BLANK_PAD_MODE 3.2 参数COMPATIBLE_MODE 3.3 参数ORDER_BY_NULLS_FLAG 3.4 参数DATETIME_FMT_MODE 3.5 参数PL_SQLCODE_COMPATIBLE 3.6 参数CALC_AS_DECIMAL 3.7 参数ENABLE_PL_SYNONYM 3.8…

三十四、VB基本知识与提高篇

一、代码编写规则: (一)标识符的使用规则: 标识符有两种:一种是系统关键字,另一种是自己定义标识符。 1、不能与系统关键字相同。 2、同一作用域(块)中不同出现重名标识符。用户自定义的标识符是不区分大小写的。 3、自定义标识符必须以字母开头,长度不能超过255…

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

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

Coppelia Sim (v-REP)仿真 机器人3D相机手眼标定与实时视觉追踪 (三)

使用标定好的结果进行跟踪标定板的位置 坐标转换的步骤为&#xff1a; 1.图像坐标点转到相机坐标系下的点 2.相机坐标系下的点转为夹爪坐标系下的点 3.夹爪坐标系下的点转为机械手极坐标系下的点 跟踪的方式 1.采用标定板的第一个坐标点作为跟踪点 3.机器人每次移动到该点位&a…

石墨舟氮气柜:半导体制造中的关键保护设备

石墨舟是由高纯度石墨材料制成的&#xff0c;主要用于承载硅片或其他基板材料通过高温处理过程&#xff0c;是制造半导体器件和太阳能电池片的关键设备之一。 石墨舟在空气中容易与氧气发生反应&#xff0c;尤其是在高温处理后&#xff0c;表面可能更为敏感&#xff1b;石墨舟具…

跟着大厂学AI | 智谱AI文本数据提取实践(大模型实战篇)

书接上回理论篇&#xff0c;本文详细介绍LLM处理模块、Prompt 构建、数据抽取后处理、数据校验、数据修复具体实战教程。 想看方案理论教程详见&#xff1a; 跟着大厂学AI | 大模型文本数据提取实践&#xff08;理论篇&#xff09;-CSDN博客文章浏览阅读2次。glm4大模型数据处…