Android高级开发第二篇 - JNI 参数传递与 Java → C → Java 双向调用

文章目录

  • Android高级开发第二篇 - JNI 参数传递与 Java → C → Java 双向调用
    • 引言
    • JNI基础回顾
    • JNI中的参数传递
      • 基本数据类型传递
      • 字符串传递
      • 数组传递
      • 对象传递
    • Java → C → Java 双向调用
      • 从C/C++调用Java方法
      • 实现一个完整的回调机制
    • 内存管理与注意事项
    • 性能优化提示
    • 结论
    • 参考资源

Android高级开发第二篇 - JNI 参数传递与 Java → C → Java 双向调用

引言

在Android开发中,JNI (Java Native Interface) 是连接Java代码和本地C/C++代码的桥梁。通过JNI,我们可以利用C/C++的高性能特性来处理计算密集型任务,同时保持Java的跨平台优势。本文将深入探讨JNI参数传递机制以及Java和C之间的双向调用实现。

JNI基础回顾

在深入参数传递之前,让我们先回顾JNI的基本概念:

  • JNI: Java本地接口,允许Java代码调用C/C++等本地语言编写的函数
  • JNIEnv: 提供大多数JNI函数的接口指针
  • jobject: 表示Java对象的引用
  • jclass: 表示Java类的引用

JNI中的参数传递

基本数据类型传递

JNI提供了一系列与Java基本数据类型对应的C数据类型:

Java类型JNI类型C/C++类型
booleanjbooleanunsigned char
bytejbytesigned char
charjcharunsigned short
shortjshortshort
intjintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble

示例代码:

// Java代码
public native int calculateSum(int a, int b);
// C代码
JNIEXPORT jint JNICALL
Java_com_example_MyClass_calculateSum(JNIEnv *env, jobject thiz, jint a, jint b) {return a + b;
}

字符串传递

字符串是最常见的复杂参数类型之一。在JNI中,我们需要在Java的String和C的字符数组之间进行转换:

// Java代码
public native String reverseString(String input);
// C代码
JNIEXPORT jstring JNICALL
Java_com_example_MyClass_reverseString(JNIEnv *env, jobject thiz, jstring input) {// 将Java字符串转换为C字符串const char* str = (*env)->GetStringUTFChars(env, input, NULL);// 处理字符串(例如反转)int len = strlen(str);char* reversed = malloc(len + 1);for (int i = 0; i < len; i++) {reversed[i] = str[len - i - 1];}reversed[len] = '\0';// 释放资源(*env)->ReleaseStringUTFChars(env, input, str);// 将C字符串转换回Java字符串jstring result = (*env)->NewStringUTF(env, reversed);free(reversed);return result;
}

数组传递

JNI提供了访问和修改Java数组的方法:

// Java代码
public native void processIntArray(int[] array);
// C代码
JNIEXPORT void JNICALL
Java_com_example_MyClass_processIntArray(JNIEnv *env, jobject thiz, jintArray array) {// 获取数组长度jsize length = (*env)->GetArrayLength(env, array);// 获取数组元素jint* elements = (*env)->GetIntArrayElements(env, array, NULL);// 处理数组for (int i = 0; i < length; i++) {elements[i] *= 2; // 每个元素乘以2}// 更新Java数组并释放资源(*env)->ReleaseIntArrayElements(env, array, elements, 0);
}

对象传递

在JNI中传递Java对象需要使用反射机制:

// Java类
public class Person {private String name;private int age;// getter和setter方法public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }
}// Java接口
public native void updatePerson(Person person);
// C代码
JNIEXPORT void JNICALL
Java_com_example_MyClass_updatePerson(JNIEnv *env, jobject thiz, jobject person) {// 获取Person类jclass personClass = (*env)->GetObjectClass(env, person);// 获取setName方法IDjmethodID setNameMethod = (*env)->GetMethodID(env, personClass, "setName", "(Ljava/lang/String;)V");// 调用setName方法jstring newName = (*env)->NewStringUTF(env, "Updated from JNI");(*env)->CallVoidMethod(env, person, setNameMethod, newName);// 获取setAge方法IDjmethodID setAgeMethod = (*env)->GetMethodID(env, personClass, "setAge", "(I)V");// 调用setAge方法(*env)->CallVoidMethod(env, person, setAgeMethod, 30);
}

Java → C → Java 双向调用

JNI最强大的特性之一是支持双向调用:不仅可以从Java调用C/C++代码,还可以从C/C++回调Java方法。

从C/C++调用Java方法

// Java类
public class Callback {// 这个方法将被C代码调用public void onProgress(int progress) {System.out.println("Progress: " + progress + "%");}// JNI方法public native void startLongTask();
}
// C代码
JNIEXPORT void JNICALL
Java_com_example_Callback_startLongTask(JNIEnv *env, jobject thiz) {// 获取Callback类jclass callbackClass = (*env)->GetObjectClass(env, thiz);// 获取onProgress方法IDjmethodID onProgressMethod = (*env)->GetMethodID(env, callbackClass, "onProgress", "(I)V");// 模拟一个长时间运行的任务for (int i = 0; i <= 100; i += 10) {// 执行一些工作...// 调用Java的回调方法(*env)->CallVoidMethod(env, thiz, onProgressMethod, i);// 模拟延迟usleep(500000); // 500毫秒}
}

实现一个完整的回调机制

下面是一个更完整的例子,展示了如何实现一个回调接口:

// Java回调接口
public interface TaskCallback {void onStart();void onProgress(int progress);void onComplete(String result);
}// Java类
public class NativeTask {private TaskCallback callback;public NativeTask(TaskCallback callback) {this.callback = callback;}// JNI方法public native void executeTask();// 静态代码块加载本地库static {System.loadLibrary("nativetask");}
}
// C代码
JNIEXPORT void JNICALL
Java_com_example_NativeTask_executeTask(JNIEnv *env, jobject thiz) {// 获取NativeTask类jclass taskClass = (*env)->GetObjectClass(env, thiz);// 获取callback字段IDjfieldID callbackField = (*env)->GetFieldID(env, taskClass, "callback", "Lcom/example/TaskCallback;");// 获取callback对象jobject callback = (*env)->GetObjectField(env, thiz, callbackField);// 获取TaskCallback接口的类引用jclass callbackClass = (*env)->FindClass(env, "com/example/TaskCallback");// 获取接口方法IDjmethodID onStartMethod = (*env)->GetMethodID(env, callbackClass, "onStart", "()V");jmethodID onProgressMethod = (*env)->GetMethodID(env, callbackClass, "onProgress", "(I)V");jmethodID onCompleteMethod = (*env)->GetMethodID(env, callbackClass, "onComplete", "(Ljava/lang/String;)V");// 调用onStart(*env)->CallVoidMethod(env, callback, onStartMethod);// 模拟任务进度for (int i = 0; i <= 100; i += 10) {// 执行一些工作...// 调用onProgress(*env)->CallVoidMethod(env, callback, onProgressMethod, i);// 模拟延迟usleep(200000); // 200毫秒}// 调用onCompletejstring result = (*env)->NewStringUTF(env, "Task completed successfully!");(*env)->CallVoidMethod(env, callback, onCompleteMethod, result);// 释放局部引用(*env)->DeleteLocalRef(env, result);
}

在实际应用中的使用示例:

// 使用示例
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TaskCallback callback = new TaskCallback() {@Overridepublic void onStart() {Log.d("NativeTask", "Task started");}@Overridepublic void onProgress(int progress) {Log.d("NativeTask", "Progress: " + progress + "%");// 更新UI进度条}@Overridepublic void onComplete(String result) {Log.d("NativeTask", "Task completed: " + result);// 显示结果}};NativeTask task = new NativeTask(callback);new Thread(() -> task.executeTask()).start();}
}

内存管理与注意事项

在JNI编程中,内存管理是一个关键问题:

  1. 局部引用: 每次JNI调用返回后自动释放,但在复杂函数中应使用DeleteLocalRef手动释放
  2. 全局引用: 必须手动创建和释放,使用NewGlobalRefDeleteGlobalRef
  3. 弱全局引用: 不会阻止垃圾回收,使用NewWeakGlobalRefDeleteWeakGlobalRef
// 创建全局引用示例
jobject globalCallback;JNIEXPORT void JNICALL
Java_com_example_NativeTask_initialize(JNIEnv *env, jobject thiz, jobject callback) {// 创建全局引用globalCallback = (*env)->NewGlobalRef(env, callback);
}JNIEXPORT void JNICALL
Java_com_example_NativeTask_cleanup(JNIEnv *env, jobject thiz) {// 释放全局引用if (globalCallback != NULL) {(*env)->DeleteGlobalRef(env, globalCallback);globalCallback = NULL;}
}

性能优化提示

  1. 最小化JNI调用次数: 每次跨越JNI边界都有开销
  2. 批量处理数据: 一次传递大量数据比多次传递少量数据更高效
  3. 直接缓冲区: 使用ByteBuffer.allocateDirect()创建直接缓冲区,减少复制
  4. 保持引用: 重复使用的类和方法ID应该缓存起来
  5. 合理释放资源: 及时释放不再需要的引用和本地资源

结论

JNI参数传递和双向调用是Android高级开发中的关键技能。掌握这些技术可以让你充分利用Java和C/C++的各自优势,构建高性能的Android应用。然而,JNI编程也带来了额外的复杂性和潜在的内存管理问题,因此需要谨慎使用并遵循最佳实践。

在实际开发中,可以考虑使用一些现代化的工具如DjinniSWIG来简化JNI开发过程,减少样板代码并提高开发效率。另外,Android NDK还提供了许多有用的库和工具,帮助开发者更轻松地进行本地开发。

参考资源

  • Android NDK 官方文档
  • JNI 规范
  • Android JNI 提示

希望本文对你理解和应用JNI参数传递与双向调用有所帮助。在下一篇文章中,我们将探讨如何在JNI中处理异常和线程安全问题。

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

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

相关文章

2025-05-28 Python深度学习8——优化器

文章目录 1 工作原理2 常见优化器2.1 SGD2.2 Adam 3 优化器参数4 学习率5 使用最佳实践 本文环境&#xff1a; Pycharm 2025.1Python 3.12.9Pytorch 2.6.0cu124 ​ 优化器 (Optimizer) 是深度学习中的核心组件&#xff0c;负责根据损失函数的梯度来更新模型的参数&#xff0c;使…

Web攻防-SQL注入增删改查HTTP头UAXFFRefererCookie无回显报错

知识点&#xff1a; 1、Web攻防-SQL注入-操作方法&增删改查 2、Web攻防-SQL注入-HTTP头&UA&Cookie 3、Web攻防-SQL注入-HTTP头&XFF&Referer 案例说明&#xff1a; 在应用中&#xff0c;存在增删改查数据的操作&#xff0c;其中SQL语句结构不一导致注入语句…

Windows MongoDB C++驱动安装

MongoDB驱动下载 MongoDB 官网MongoDB C驱动程序入门MongoDB C驱动程序入门 安装环境 安装CMAKE安装Visual Studio 编译MongoDB C驱动 C驱动依赖C驱动&#xff0c;需要先编译C驱动 下载MongoDB C驱动源码 打开CMAKE(cmake-gui) 选择源码及输出路径,然后点击configure …

使用 C/C++ 和 OpenCV 调用摄像头

使用 C/C 和 OpenCV 调用摄像头 &#x1f4f8; OpenCV 是一个强大的计算机视觉库&#xff0c;它使得从摄像头捕获和处理视频流变得非常简单。本文将指导你如何使用 C/C 和 OpenCV 来调用摄像头、读取视频帧并进行显示。 准备工作 在开始之前&#xff0c;请确保你已经正确安装…

使用微软最近开源的WSL在Windows上优雅的运行Linux

install wsl https://github.com/microsoft/WSL/releases/download/2.4.13/wsl.2.4.13.0.x64.msi install any distribution from microsoft store, such as kali-linux from Kali office website list of distribution PS C:\Users\50240> wsl -l -o 以下是可安装的有…

Win11安装Dify

1、打开Virtual Machine Platform功能 电脑系统为&#xff1a;Windows 11 家庭中文版24H2版本。 打开控制面板&#xff0c;点击“程序”&#xff0c;点击“启用或关闭Windows功能”。 下图标记的“Virtual Machine Platform”、“适用于 Linux 的 Windows 子系统”、“Windows…

C++模板类深度解析与气象领域应用指南

支持开源&#xff0c;为了更好的后来者 ————————————————————————————————————————————————————By 我说的 C模板类深度解析与气象领域应用指南 一、模板类核心概念 1.1 模板类定义 模板类是C泛型编程的核心机制&#x…

MongoDB(七) - MongoDB副本集安装与配置

文章目录 前言一、下载MongoDB1. 下载MongoDB2. 上传安装包3. 创建相关目录 二、安装配置MongoDB1. 解压MongoDB安装包2. 重命名MongoDB文件夹名称3. 修改配置文件4. 分发MongoDB文件夹5. 配置环境变量6. 启动副本集7. 进入MongoDB客户端8. 初始化副本集8.1 初始化副本集8.2 添…

mac笔记本如何快捷键截图后自动复制到粘贴板

前提&#xff1a;之前只会进行部分区域截图操作&#xff08;commandshift4&#xff09;操作&#xff0c;截图后发现未自动保存在剪贴板&#xff0c;还要进行一步手动复制到剪贴板的操作。 mac笔记本如何快捷键截图后自动复制到粘贴板 截取 Mac 屏幕的一部分并将其自动复制到剪…

WPF 按钮点击音效实现

WPF 按钮点击音效实现 下面我将为您提供一个完整的 WPF 按钮点击音效实现方案&#xff0c;包含多种实现方式和高级功能&#xff1a; 完整实现方案 MainWindow.xaml <Window x:Class"ButtonClickSound.MainWindow"xmlns"http://schemas.microsoft.com/win…

C++ list基础概念、list初始化、list赋值操作、list大小操作、list数据插入

list基础概念&#xff1a;list中的每一部分是一个Node&#xff0c;由三部分组成&#xff1a;val、next、prev&#xff08;指向上一个节点的指针&#xff09; list初始化的代码&#xff0c;见下 #include<iostream> #include<list>using namespace std;void printL…

【Pandas】pandas DataFrame equals

Pandas2.2 DataFrame Reindexing selection label manipulation 方法描述DataFrame.add_prefix(prefix[, axis])用于在 DataFrame 的行标签或列标签前添加指定前缀的方法DataFrame.add_suffix(suffix[, axis])用于在 DataFrame 的行标签或列标签后添加指定后缀的方法DataFram…

【ROS2】创建单独的launch包

【ROS】郭老二博文之:ROS目录 1、简述 项目中,可以创建单独的launch包来管理所有的节点启动 2、示例 1)创建launch包(python) ros2 pkg create --build-type ament_python laoer_launch --license Apache-2.02)创建启动文件 先创建目录:launch 在目录中创建文件:r…

GitHub 趋势日报 (2025年05月23日)

本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1All-Hands-AI/OpenHands&#x1f64c;开放式&#xff1a;少代码&#xff0c;做…

鸿蒙OSUniApp 实现的数据可视化图表组件#三方框架 #Uniapp

UniApp 实现的数据可视化图表组件 前言 在移动互联网时代&#xff0c;数据可视化已成为产品展示和决策分析的重要手段。无论是运营后台、健康监测、还是电商分析&#xff0c;图表组件都能让数据一目了然。UniApp 作为一款优秀的跨平台开发框架&#xff0c;支持在鸿蒙&#xf…

[ctfshow web入门] web124

信息收集 error_reporting(0); //听说你很喜欢数学&#xff0c;不知道你是否爱它胜过爱flag if(!isset($_GET[c])){show_source(__FILE__); }else{//例子 c20-1$content $_GET[c];// 长度不允许超过80个字符if (strlen($content) > 80) {die("太长了不会算");}/…

Vue 技术文档

一、引言 Vue 是一款用于构建用户界面的渐进式 JavaScript 框架&#xff0c;具有易上手、高性能、灵活等特点&#xff0c;能够帮助开发者快速开发出响应式的单页面应用。本技术文档旨在全面介绍 Vue 的相关技术知识&#xff0c;为开发人员提供参考和指导。 二、环境搭建 2.1…

Nodejs+http-server 使用 http-server 快速搭建本地图片访问服务

在开发过程中&#xff0c;我们经常需要临时查看或分享本地的图片资源&#xff0c;比如设计稿、截图、素材等。虽然可以通过压缩发送&#xff0c;但效率不高。本文将教你使用 Node.js 的一个轻量级工具 —— http-server&#xff0c;快速搭建一个本地 HTTP 图片预览服务&#xf…

通义智文开源QwenLong-L1: 迈向长上下文大推理模型的强化学习

&#x1f389; 动态 2025年5月26日: &#x1f525; 我们正式发布&#x1f917;QwenLong-L1-32B——首个采用强化学习训练、专攻长文本推理的LRM模型。在七项长文本文档问答基准测试中&#xff0c;QwenLong-L1-32B性能超越OpenAI-o3-mini和Qwen3-235B-A22B等旗舰LRM&#xff0c…

学习如何设计大规模系统,为系统设计面试做准备!

前言 在当今快速发展的技术时代&#xff0c;系统设计能力已成为衡量一名软件工程师专业素养的重要标尺。随着云计算、大数据、人工智能等领域的兴起&#xff0c;构建高性能、可扩展且稳定的系统已成为企业成功的关键。然而&#xff0c;对于许多工程师而言&#xff0c;如何有效…