当前位置: 首页 > article >正文

Native代码与Java的交互艺术——访问字段、调用方法

在 Android 开发、高性能计算或遗留系统整合中Java 与 Native 代码C/C的交互JNI是不可或缺的技能。本文将以实战为导向详细讲解如何在 Native 层访问 Java 对象字段、调用实例与静态方法、处理字符串与数组并重点介绍性能优化技巧ID 缓存及常见陷阱。掌握这些内容你将具备打通 Java 与 Native 双向通信的核心能力。1. 前言Java 凭借跨平台和内存安全特性成为应用层开发的首选但在涉及底层硬件访问、复杂算法或高性能图形处理时C/C 依然是王者。JNIJava Native Interface便是连接这两座岛屿的桥梁。很多开发者对 JNI 的理解仅停留在“传递基本类型参数”一旦涉及操作 Java 对象内部状态或回调 Java 方法往往容易遇到签名错误、内存泄漏或性能瓶颈。本文将拆解这些核心交互场景助你写出健壮高效的 Native 代码。2. 准备工作环境与方法签名在开始之前我们需要明确一个核心概念方法签名Method Signature。JNI 通过签名来识别字段和方法格式严格不能出错。2.1 常见类型签名对照表Java 类型JNI 类型签名字符boolean jboolean ZbytejbyteBcharjcharCshortjshortSintjintIlongjlongJfloatjfloatFdoublejdoubleDvoidvoidVStringjstringLjava/lang/String;ObjectjobjectLjava/lang/Object;int[]jintArray[I方法签名格式(参数类型签名)返回值类型签名例如public void setName(String name) → (Ljava/lang/String;)Vpublic static int add(int a, int b) → (II)I技巧可使用 javap -s 类名.class 命令快速获取方法签名避免手写出错。3. 核心实战访问与修改 Java 字段在 Native 代码中我们不仅可以读取传入的参数还可以直接修改 Java 对象的成员变量。3.1 Java 层定义// User.javapublicclassUser{publicintid;publicStringname;publicUser(intid,Stringname){this.idid;this.namename;}}3.2 Native 层操作#includejni.h#includestring.h#includeandroid/log.h#defineLOG_TAGJNI_DEMO#defineLOGI(...)__android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)// 假设这是由 Java 调用的 Native 方法// public native void modifyUser(User user);JNIEXPORTvoidJNICALLJava_com_example_jnidemo_MainActivity_modifyUser(JNIEnv*env,jobject thiz,jobject userObj){// 1. 获取 Class 引用jclass userClass(*env)-GetObjectClass(env,userObj);// 2. 获取字段 ID (FieldID)jfieldID idFieldId(*env)-GetFieldID(env,userClass,id,I);jfieldID nameFieldId(*env)-GetFieldID(env,userClass,name,Ljava/lang/String;);if(idFieldIdNULL||nameFieldIdNULL){LOGI(Field not found!);return;}// 3. 读取字段值jint currentId(*env)-GetIntField(env,userObj,idFieldId);LOGI(Original ID: %d,currentId);// 4. 修改字段值 (写操作)(*env)-SetIntField(env,userObj,idFieldId,10086);// 5. 处理字符串字段jstring nameStr(jstring)(*env)-GetObjectField(env,userObj,nameFieldId);constchar*nameChars(*env)-GetStringUTFChars(env,nameStr,NULL);LOGI(Original Name: %s,nameChars);// 创建新字符串并设置回去jstring newName(*env)-NewStringUTF(env,Native_Hacker);(*env)-SetObjectField(env,userObj,nameFieldId,newName);// 6. 释放字符串内存 (非常重要)(*env)-ReleaseStringUTFChars(env,nameStr,nameChars);// 7. 删除局部引用 (防止溢出)(*env)-DeleteLocalRef(env,newName);(*env)-DeleteLocalRef(env,nameStr);(*env)-DeleteLocalRef(env,userClass);}关键点解析GetFieldID 是获取字段入口需要准确的签名。GetXxxField 和 SetXxxField 成对出现类型必须匹配如 GetIntField 对应 int。内存管理GetStringUTFChars 分配的内存必须通过 ReleaseStringUTFChars 释放否则会导致内存泄漏。3.3 访问静态字段静态字段属于类本身操作时使用 jclass 而非 jobject// 读取静态字段jint staticInt(*env)-GetStaticIntField(env,clazz,staticFieldId);// 修改静态字段(*env)-SetStaticIntField(env,clazz,staticFieldId,newValue);4. 核心实战调用 Java 方法Native 代码不仅可以“读/写”数据还可以“指挥”Java 对象执行逻辑例如回调通知、更新 UI 等。4.1 Java 层定义// User.java 追加方法publicclassUser{// ... 字段省略// 实例方法publicvoidupdateInfo(intnewId){this.idnewId;Log.d(User,Info updated to: newId);}// 静态方法publicstaticintgetVersionCode(){return100;}}4.2 Native 层调用JNIEXPORTvoidJNICALLJava_com_example_jnidemo_MainActivity_callJavaMethods(JNIEnv*env,jobject thiz,jobject userObj){jclass userClass(*env)-GetObjectClass(env,userObj);// --- 调用实例方法 ---jmethodID updateMethodId(*env)-GetMethodID(env,userClass,updateInfo,(I)V);if(updateMethodId!NULL){(*env)-CallVoidMethod(env,userObj,updateMethodId,9527);// 检查是否有异常抛出 (Java 层可能抛异常)if((*env)-ExceptionCheck(env)){(*env)-ExceptionDescribe(env);(*env)-ExceptionClear(env);}}// --- 调用静态方法 ---jmethodID staticMethodId(*env)-GetStaticMethodID(env,userClass,getVersionCode,()I);if(staticMethodId!NULL){jint version(*env)-CallStaticIntMethod(env,userClass,staticMethodId);LOGI(Java Version Code from Native: %d,version);}(*env)-DeleteLocalRef(env,userClass);}关键点解析实例 vs 静态实例方法用 GetMethodID CallXxxMethod静态方法用 GetStaticMethodID CallStaticXxxMethod。异常处理Java 方法可能抛出异常Native 层不会自动崩溃但后续 JNI 调用会失效。务必使用 ExceptionCheck 和 ExceptionClear 处理。返回值CallXxxMethod 系列函数根据返回值类型不同有多个变种如 CallIntMethod, CallObjectMethod 等。4.3 调用构造函数创建 Java 对象jobjectcreateUser(JNIEnv*env,jint id,constchar*name){jclass userClass(*env)-FindClass(env,com/example/User);jmethodID constructor(*env)-GetMethodID(env,userClass,init,(ILjava/lang/String;)V);jstring jname(*env)-NewStringUTF(env,name);jobject user(*env)-NewObject(env,userClass,constructor,id,jname);(*env)-DeleteLocalRef(env,jname);(*env)-DeleteLocalRef(env,userClass);returnuser;}5. 复杂数据处理数组交互数组在数据批量传输中非常常见。直接通过索引逐个访问GetXxxArrayRegion效率较低推荐使用“指针映射”方式。5.1 Java 层publicnativeint[]processArray(int[]input);5.2 Native 层JNIEXPORT jintArray JNICALLJava_com_example_jnidemo_MainActivity_processArray(JNIEnv*env,jobject thiz,jintArray inputArray){// 1. 获取数组长度jsize length(*env)-GetArrayLength(env,inputArray);// 2. 获取数组元素指针jint*elements(*env)-GetIntArrayElements(env,inputArray,NULL);if(elementsNULL)returnNULL;// OOM// 3. 像操作 C 数组一样操作for(inti0;ilength;i){elements[i]elements[i]*2;}// 4. 释放并同步回 Java 数组// 第三个参数 0 表示复制回 Java 数组并释放 C 指针(*env)-ReleaseIntArrayElements(env,inputArray,elements,0);// 5. 返回结果 (这里直接返回原数组引用也可以创建新数组)returninputArray;}释放模式标志位0复制修改回 Java 数组释放 C 指针。JNI_COMMIT复制修改回 Java 数组不释放 C 指针可继续修改。JNI_ABORT不复制修改直接释放 C 指针用于只读场景提升性能。5.3 对象数组操作// 获取对象数组元素jobject obj(*env)-GetObjectArrayElement(env,objArray,index);// 设置对象数组元素(*env)-SetObjectArrayElement(env,objArray,index,newObj);6. 性能优化缓存字段与方法 ID6.1 为什么要缓存FindClass, GetFieldID, GetMethodID 都是高开销操作。它们涉及字符串查找和哈希计算。如果在循环中或高频调用的 Native 方法中每次都执行这些操作性能会急剧下降。6.2 缓存策略利用 static 全局变量存储 jclass, jfieldID, jmethodID。由于 jclass 是局部引用在方法返回后会失效因此需要创建全局引用 (NewGlobalRef)。// 全局缓存变量staticjclass g_UserClassNULL;staticjmethodID g_updateMethodIdNULL;staticjfieldID g_idFieldIdNULL;// 初始化函数 (在 JNI_OnLoad 或首次调用时执行)voidinitCache(JNIEnv*env){if(g_UserClass!NULL)return;// 已初始化jclass localClass(*env)-FindClass(env,com/example/jnidemo/User);if(localClassNULL)return;// 创建全局引用防止被 GC 回收g_UserClass(jclass)(*env)-NewGlobalRef(env,localClass);// 缓存 IDg_updateMethodId(*env)-GetMethodID(env,g_UserClass,updateInfo,(I)V);g_idFieldId(*env)-GetFieldID(env,g_UserClass,id,I);// 删除局部引用(*env)-DeleteLocalRef(env,localClass);}// 清理函数 (在 JNI_OnUnload 中执行)voidreleaseCache(JNIEnv*env){if(g_UserClass!NULL){(*env)-DeleteGlobalRef(env,g_UserClass);g_UserClassNULL;g_updateMethodIdNULL;g_idFieldIdNULL;}}// 业务方法直接使用缓存JNIEXPORTvoidJNICALLJava_com_example_jnidemo_MainActivity_fastCall(JNIEnv*env,jobject thiz,jobject userObj){// 确保初始化initCache(env);// 直接使用全局缓存无需查找(*env)-CallVoidMethod(env,userObj,g_updateMethodId,123);(*env)-SetIntField(env,userObj,g_idFieldId,123);}6.3 在 JNI_OnLoad 中初始化JNIEXPORT jint JNICALLJNI_OnLoad(JavaVM*vm,void*reserved){JNIEnv*env;if((*vm)-GetEnv(vm,(void**)env,JNI_VERSION_1_6)!JNI_OK){returnJNI_ERR;}// 初始化缓存initCache(env);returnJNI_VERSION_1_6;}7. 避坑指南与最佳实践7.1 局部引用溢出JNI 中创建的局部引用如 FindClass, NewStringUTF 的返回值默认在 Native 方法返回时释放。如果在循环中创建了大量局部引用可能导致局部引用表溢出。解决方案在循环内使用 DeleteLocalRef 及时清理。使用 PushLocalFrame / PopLocalFrame 批量管理。for(inti0;i10000;i){(*env)-PushLocalFrame(env,10);// 创建新帧最多容纳 10 个局部引用jclass cls(*env)-FindClass(env,java/lang/Object);// ... 使用 cls(*env)-PopLocalFrame(env,NULL);// 释放当前帧的所有局部引用}7.2 线程问题JNIEnv* 是线程局部的不能在线程间传递。如果在子线程调用 JNI必须先调用 AttachCurrentThread 获取当前线程的 JNIEnv*使用完后 DetachCurrentThread。void*thread_func(void*arg){JavaVM*jvm(JavaVM*)arg;JNIEnv*env;(*jvm)-AttachCurrentThread(jvm,env,NULL);// 使用 env 进行 JNI 操作(*jvm)-DetachCurrentThread(jvm);returnNULL;}7.3 字符串编码GetStringUTFChars 返回的是修改过的 UTF-8 字符串可能不包含完整的 Unicode 信息如 Emoji。如果需要完整 Unicode请使用 GetStringChars (UTF-16)。7.4 空指针检查任何 Get…ID 或 FindClass 的返回值都必须检查是否为 NULL否则后续调用会导致 Native Crash。7.5 异常处理每次调用 Java 方法后建议检查异常(*env)-CallVoidMethod(env,obj,methodId);if((*env)-ExceptionCheck(env)){(*env)-ExceptionDescribe(env);// 打印堆栈(*env)-ExceptionClear(env);// 清除异常// 处理错误例如返回错误码}8. 总结掌握 Native 与 Java 的交互关键在于理解 JNI 接口函数的命名规则、类型签名的匹配以及内存生命周期的管理。字段访问GetFieldID Get/SetXxxField方法调用GetMethodID CallXxxMethod注意静态方法区别数据处理字符串和数组操作后务必 Release性能高频调用的 ID 务必缓存为全局引用通过本文的实战代码相信你已经能够构建出稳定、高效的 JNI 桥梁。在实际项目中建议结合 CMake 构建系统并充分利用 Android Studio 的 Profiler 工具监控 Native 内存确保交互过程的安全与流畅。‍ 觉得有帮助请点赞、收藏、关注后续带来更多 JNI/NDK 实战技巧 有任何问题欢迎评论区交流讨论

相关文章:

Native代码与Java的交互艺术——访问字段、调用方法

在 Android 开发、高性能计算或遗留系统整合中,Java 与 Native 代码(C/C)的交互(JNI)是不可或缺的技能。本文将以实战为导向,详细讲解如何在 Native 层访问 Java 对象字段、调用实例与静态方法、处理字符串…...

【数据结构】二叉树入门全解:从定义、性质到经典真题

一、先搞懂:什么是二叉树?二叉树(Binary Tree)是一种特殊的树形结构,定义非常清晰:它是由 n(n≥0) 个结点构成的有限集合,满足:空树:当 n0 时&…...

3个简单技巧让YOLO小目标检测精度提升50%:Ultralytics实战指南

3个简单技巧让YOLO小目标检测精度提升50%:Ultralytics实战指南 【免费下载链接】ultralytics Ultralytics YOLO 🚀 项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics 你是否在为监控视频中远处行人检测不准而烦恼?工业质…...

从‘数值灾难’到平稳训练:深入浅出聊聊MoE中路由Z-loss的设计哲学

从‘数值灾难’到平稳训练:深入浅出聊聊MoE中路由Z-loss的设计哲学 想象一下,你正在指挥一个由数百名专家组成的交响乐团。每位音乐家都技艺精湛,但如果在演奏时某个乐器的音量突然爆表(比如小号手过于兴奋)&#xff…...

一码一物的生成软件,为什么总能先把窜货和返利黑洞堵住?

一码一物的生成软件,为什么总能先把窜货和返利黑洞堵住?很多老板嘴上说生意难做,真把账摊开看,难的不是卖不出去,而是货卖到哪儿不知道、钱花给谁不清楚、促销有没有真拉动更说不明白。一码一物的生成软件,…...

TDEFNODE 安装与入门:从源码编译到成功跑通案例(超详细避坑指南)

TDEFNODE 安装与入门:从源码编译到成功跑通案例(超详细避坑指南) 一、前言 TDEFNODE 是一个用于地壳形变建模的经典科研程序,常用于 GNSS 速度场反演、块体运动分析以及断层滑动研究。 但与常见软件不同:TDEFNODE 不是…...

OpenClaw开发环境配置:千问3.5-9B辅助的IDE插件管理

OpenClaw开发环境配置:千问3.5-9B辅助的IDE插件管理 1. 为什么需要AI辅助的IDE管理 作为一个长期在多个项目间切换的全栈开发者,我深受开发环境配置问题的困扰。每次换新电脑或者重装系统,光是配置VSCode插件和项目依赖就要耗费大半天时间。…...

五层电梯MCGS7.7嵌入版与三菱PLC的联动编程实践

5五层电梯MCGS7.7嵌入版和三菱PLC联机程序调试电梯控制程序最头疼的莫过于通讯不稳定。上个月刚搞完一个五层电梯项目,MCGS7.7触摸屏和三菱FX3U的联机调试过程简直像坐过山车——楼层显示乱跳、按钮状态丢失这些幺蛾子接踵而来。今天咱就唠唠这个项目的实战经验。硬…...

新一代高端工业 HMI 如何重塑现场交互体验?

繁易 FPADX 系列电容触摸屏支持 3D 可视化、多点触控、Web 远程访问与大型工程承载,帮助工业设备实现更高效、更直观、更智能的人机交互体验。在工业自动化持续升级的今天,触摸屏早已不再只是设备上的一个操作界面。对于设备制造商、系统集成商和终端工厂…...

第三方软件测评机构中CMA与CNAS资质对软件验收的重要性

CMA与CNAS资质的重要性 在软件项目验收过程中,第三方软件测评机构的CMA(中国计量认证)与CNAS(中国合格评定国家认可委员会)资质至关重要。这些资质不仅是机构专业能力的体现,更是确保测试结果公正、准确、可…...

2026 codex 大模型 api 配置指南:auth.json、config.toml 与 401/超时排查

当 codex --version 已经能正常输出,很多人会以为接下来只剩下提问和改代码。但真正决定 Codex 能不能顺利进入项目的,往往是 codex 大模型 api 有没有按要求接好:只要 auth.json、config.toml 或网关地址有一点偏差,就可能马上碰…...

告别窗口闪烁:用BLASTSyncEngine实现Android多窗口平滑过渡的完整指南

告别窗口闪烁:用BLASTSyncEngine实现Android多窗口平滑过渡的完整指南 在Android多窗口交互场景中,开发者经常面临一个棘手问题——当用户进行分屏切换、画中画调整或任务栈重组时,窗口内容会出现短暂闪烁或撕裂。这种视觉瑕疵不仅影响用户体…...

PagerDuty与NodeJS集成:构建高效监控告警系统的实践指南

1. 为什么需要PagerDuty与NodeJS集成? 在当今的互联网服务架构中,系统的稳定性和可用性至关重要。想象一下,如果你的电商网站在凌晨3点突然宕机,而整个团队都在熟睡中,这会导致多少订单流失?这就是监控告警…...

Python无锁并发避坑手册(20年C Python核心贡献者亲授:从字节码级锁定到原子内存序的17个致命盲区)

第一章:Python无锁并发的本质与GIL真相Python常被误认为“天生支持多线程并发”,但其核心限制源于全局解释器锁(Global Interpreter Lock, GIL)。GIL并非语言规范,而是CPython解释器为内存管理安全而引入的互斥机制——…...

电子元器件失效分析与预防实战指南

1. 电子元器件失效的底层逻辑剖析 电子元器件失效的本质是材料特性、环境应力与时间因素共同作用的结果。作为一名硬件工程师,我处理过数百例元器件失效案例,发现失效模式往往遵循"应力-损伤-失效"的因果链。理解这个链条,才能从根…...

Qclaw 效率工作流实战测评:让微信变成你的「远程生产力中枢」

一句微信消息,驱动电脑自动干活——这不是概念片,是我用了两周 Qclaw 后的真实体感。 一、Qclaw 是什么?30 秒讲清楚 qclaw Qclaw 是腾讯电脑管家团队出品的个人 AI Agent 工具,基于开源框架 OpenClaw 封装而成。核心逻辑用一句…...

HGD运动想象脑电数据集预处理实战:从数据加载到特征标准化

1. HGD数据集简介与下载指南 HGD(High Gamma Dataset)是目前运动想象脑电研究领域最常用的公开数据集之一,由德国柏林工业大学团队采集并开源。这个数据集包含了14名受试者在执行左手、右手、脚部和休息四种运动想象任务时的高密度脑电信号&a…...

ThinkLink+EdgeBus 将建大仁科的氧传感器接入到LoRaWAN系统

传统 RS485 传感器,也能快速接入 LoRaWAN 系统很多项目现场,其实已经部署了不少成熟可用的传感器。 问题往往不在于“传感器能不能测”,而在于:怎样把这些传统传感器,快速接入 LoRaWAN 和上层业务系统?以 R…...

深入解析pysim中的eUICC ISD-R命令:从基础操作到高级应用

1. eUICC ISD-R命令基础入门 第一次接触eUICC ISD-R命令时,我完全被那些专业术语搞晕了。经过几个项目的实战,我发现这些命令其实就像智能手机上的应用商店操作——只不过管理的是SIM卡上的应用。eUICC(嵌入式通用集成电路卡)是现…...

OpenClaw环境迁移:gemma-3-12b-it配置备份与恢复指南

OpenClaw环境迁移:gemma-3-12b-it配置备份与恢复指南 1. 为什么需要环境迁移方案 上周我的主力开发机突然硬盘故障,导致所有数据丢失。最让我头疼的不是代码仓库——它们都有远程备份,而是那套精心调校的OpenClawgemma-3-12b-it环境。花了整…...

雷军5小时拆车直播爆火!硬核技术成新风口,自媒体可直接做

4月2日晚,雷军5小时直播拆解新一代SU7引发全网热议,单场观看量突破1亿,弹幕满是“硬核”“专业”的好评。这场直播颠覆了技术内容的传播模式,从“参数堆砌”转向“实证拆解”,从“单向宣讲”升级为“双向互动”&#x…...

量子态可视化太难?用C++ + ImGUI实时渲染Bloch球+概率幅热力图(含跨平台编译脚本)

第一章:量子态可视化太难?用C ImGUI实时渲染Bloch球概率幅热力图(含跨平台编译脚本)量子计算教学与算法调试中,单量子比特态的几何表示——Bloch球——是理解叠加、相位与测量的核心工具;而复数概率幅的模…...

扩散模型对抗样本经典baselines

1. 流图:数据的河流 如果把传统的堆叠面积图想象成一块块整齐堆叠的积木,那么流图就像一条蜿蜒流淌的河流,河道的宽窄变化自然流畅,波峰波谷过渡平滑。 它特别适合展示多个类别数据随时间的变化趋势,尤其是当你想强调整…...

大规模模型训练卡在92%?PyTorch 3.0静态图分布式调试全流程:从Graph IR Dump到Device Placement热力图分析

第一章:PyTorch 3.0静态图分布式训练全景概览PyTorch 3.0 引入了原生静态图编译能力(TorchDynamo Inductor 后端深度集成),结合 torch.distributed 的增强型 SPMD(Single Program, Multiple Data)抽象&…...

嵌入式开发语言选择:C与C++的实战对比

1. 嵌入式开发语言选择的核心考量在嵌入式系统开发领域,C和C的争论已经持续了数十年。作为一名在工业控制和消费电子领域工作多年的嵌入式工程师,我见证了从8位单片机到多核处理器的演进过程。选择开发语言绝非简单的技术偏好问题,而是需要综…...

2026届毕业生推荐的十大降重复率神器解析与推荐

Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 降低AIGC痕迹的关键之处在于去除机器生成的那种模式化特性,如果要采用避免使用过…...

【全球首批C++27静态反射商用项目解密】:西门子PLC配置引擎重构实测——编译时间+12%,运行时内存下降93.7%

第一章:C27静态反射工业应用案例C27引入的静态反射(Static Reflection)核心特性——基于std::reflexpr与编译期元对象模型(Meta Object Model, MOM)——已进入关键工业验证阶段。多家汽车电子与工业控制厂商在AUTOSAR …...

Mac开发者必备:OpenClaw联动千问3.5-27B实现代码审查自动化

Mac开发者必备:OpenClaw联动千问3.5-27B实现代码审查自动化 1. 为什么需要代码审查自动化? 作为独立开发者,我经常面临一个尴尬局面:在深夜提交代码后,第二天才发现引入了低级语法错误或潜在漏洞。传统CI工具虽然能捕…...

数据科学家稳健统计系列第一部分:稳健的中心趋势度量以及...

原文:towardsdatascience.com/robust-statistics-for-data-scientists-part-1-resilient-measures-of-central-tendency-and-67e5a60b8bf1 https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/cf43c75d8b50af4d9c13df54abeccde8.pn…...

生产环境Python 3.14 JIT崩溃率突增400%?,资深SRE团队紧急封存的8个未公开__PyJIT_TraceConfig参数调优组合

第一章:Python 3.14 JIT 编译器性能调优生产环境部署全景图Python 3.14 引入的原生 JIT 编译器(代号 “PyJIT”)标志着 CPython 运行时架构的重大演进。它不再依赖外部工具链(如 Cython 或 Numba),而是以内…...