4.2 Android NDK 基础概念

1 JavaVMJNIEnv

  JNI 定义了两个关键数据结构,JavaVMJNIEnv。这两者本质上都是指向函数表指针的指针。(在 C++ 版本中,它们是具有指向函数表的指针的类,以及指向该表的每个 JNI 函数的成员函数。)JavaVM提供了“调用接口”函数,允许您创建和销毁JavaVM。理论上,每个进程可以有多个JavaVM,但 Android 只允许一个。

  JNIEnv提供了大部分 JNI 功能。除了@CriticalNative方法外,您的原生函数都会收到JNIEnv作为第一个参数。

  JNIEnv用于线程本地存储。因此,您不能在线程之间共享JNIEnv。如果一段代码没有其他方法获取其JNIEnv,则应共享JavaVM,并使用GetEnv发现线程的JNIEnv。(假设它有一个)

  JNIEnvJavaVM的 C 声明与 C++ 声明不同。jni.h包含文件提供不同的typedef,具体取决于它是包含在 C 还是 C++ 中。因此,在两种语言都包含的头文件中包含JNIEnv参数是一个坏主意。(换句话说:如果你的头文件需要#ifdef __cplusplus,如果该头文件中的任何内容引用JNIEnv,你可能需要做一些额外的工作。)

2 jclassjmethodIDjfieldID

  如果要从原生代码访问对象的字段,可以执行以下操作:

  • 使用FindClass获取类的类对象引用
  • 使用GetFieldID获取字段的字段 ID
  • 使用适当的东西获取字段的内容,例如GetIntField

  同样,要调用一个方法,您首先会得到一个类对象引用,然后是一个方法 ID。这些 ID 通常只是指向内部运行时数据结构的指针。查找它们可能需要几个字符串比较,但一旦你有了它们,获取字段或调用方法的实际调用就非常快了。

  如果性能很重要,那么查找一次值并将结果缓存在原生代码中是有用的。因为每个进程只能有一个JavaVM,所以将这些数据存储在静态本地结构中是合理的。

  类引用、字段 ID 和方法 ID 保证有效,直到类被卸载。只有当与ClassLoader关联的所有类都可以被垃圾回收时,类才会被卸载,这在 Android 中很少见,但并非不可能。但是请注意,jclass是一个类引用,必须通过调用NewGlobalRef来保护它。

  如果你想在加载类时缓存 ID,并在卸载和重新加载类时自动重新缓存它们,初始化 ID 的正确方法是在相应的类中添加一段看起来像这样的代码:

    /** We use a class initializer to allow the native code to cache some* field offsets. This native function looks up and caches interesting* class/field/method IDs. Throws on failure.*/private static native void nativeInit();static {nativeInit();}

  在 C/C++ 代码中创建一个执行 ID 查找的nativeClassInit方法。代码将在类初始化时执行一次。如果类被卸载然后重新加载,它将再次执行。

3 局部和全局引用

  传递给原生方法的每个参数以及 JNI 函数返回的几乎每个对象都是“局部引用”。这意味着它在当前线程中当前原生方法的持续时间内有效。即使对象本身在原生方法返回后继续存在,引用也是无效的。

  这适用于jobject的所有子类,包括jclassjstringjarray。(启用扩展 JNI 检查时,运行时将警告您大多数引用错误使用。)

  获取非局部引用的唯一方法是通过函数NewGlobalRefNewWeakGlobalRef

  如果你想在更长的时间内保留一个引用,你必须使用“全局”引用。NewGlobalRef函数将局部引用作为参数并返回全局引用。在您调用DeleteGlobalRef之前,全局引用保证有效。

  此模式通常用于缓存从FindClass返回的jclass,例如:

jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));

  所有 JNI 方法都接受局部和全局引用作为参数。对同一对象的引用可能具有不同的值。例如,对同一对象连续调用NewGlobalRef的返回值可能不同。要查看两个引用是否引用同一个对象,必须使用IsSameObject函数。切勿在原生代码中使用==比较引用。

  这样做的一个后果是,您不能假设对象引用在原生代码中是恒定的或唯一的。表示对象的值可能因方法的一次调用而不同,并且两个不同的对象在连续调用时可能具有相同的值。不要将jobject值用作键。

  程序员被要求“不要过度分配”局部引用。实际上,这意味着,如果您正在创建大量的局部引用,也许是在运行一组对象时,您应该使用DeleteLocalRef手动释放它们,而不是让 JNI 为您完成。该实现只需要为 16 个局部引用保留插槽,因此如果您需要更多,您应该边走边删除,或者使用EnsureLocalCapacity/PushLocalFrame保留更多。

  请注意,jfieldIDjmethodID是不透明类型,不是对象引用,不应传递给NewGlobalRefGetStringUTFCharGetByteArrayElements等函数返回的原始数据指针也不是对象。(它们可以在线程之间传递,并且在匹配的Release调用之前有效。)

  一个不寻常的案例值得单独提及。如果使用AttachCurrentThread附加原生线程,则在线程分离之前,您正在运行的代码将永远不会自动释放局部引用。您创建的任何局部引用都必须手动删除。一般来说,任何在循环中创建局部引用的原生代码都可能需要手动删除。

  使用全局引用时要小心。全局引用可能是不可避免的,但它们很难调试,并可能导致难以诊断的内存(错误)行为。在其他条件相同的情况下,全局引用较少的解决方案可能更好。

4 原生库

  您可以使用标准System.loadLibrary从共享库加载原生代码。

  从静态类初始化器调用System.loadLibrary,参数是“未修饰”的库名称,因此要加载libfubar.so,您需要传入fubar

  如果你只有一个具有原生方法的类,那么在该类的静态初始化器中调用System.loadLibrary是有意义的。否则,您可能希望从Application进行调用,这样您就知道库总是加载的,并且总是提前加载。

  运行时可以通过两种方式找到您的原生方法。您可以使用RegisterNatives显式注册它们,也可以让运行时使用dlsym动态查找它们。RegisterNatives的优点是,您可以预先检查符号是否存在,此外,通过只导出JNI_OnLoad,您可以拥有更小、更快的共享库。让运行时发现您的函数的优点是,编写的代码稍微少一些。

  要使用RegisterNatives,请执行以下操作:

  • 提供JNIEXPORT jint JNI_OnLoad(JavaVM* vm,void* reserved)函数。
  • JNI_OnLoad中,使用RegisterNatives注册所有原生方法。
  • 使用版本脚本(首选)进行构建,或使用-fvisibility=hidden,以便仅从库中导出JNI_OnLoad。这会生成更快、更小的代码,并避免与加载到应用程序中的其他库发生潜在冲突(但如果应用程序在原生代码中崩溃,它会创建不太有用的堆栈跟踪)。

  静态初始化器应该如下所示:

static {System.loadLibrary("fubar");
}

  如果用 C++ 编写,JNI_OnLoad函数应该看起来像这样:

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env;if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}// Find your class. JNI_OnLoad is called from the correct class loader context for this to work.jclass c = env->FindClass("com/example/app/package/MyClass");if (c == nullptr) return JNI_ERR;// Register your class' native methods.static const JNINativeMethod methods[] = {{"nativeFoo", "()V", reinterpret_cast<void*>(nativeFoo)},{"nativeBar", "(Ljava/lang/String;I)Z", reinterpret_cast<void*>(nativeBar)},};int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));if (rc != JNI_OK) return rc;return JNI_VERSION_1_6;
}

  要使用原生方法的“发现”,您需要以特定的方式命名它们。这意味着,如果一个方法签名是错误的,你只有在第一次实际调用该方法时才会知道。

  从JNI_OnLoad进行的任何FindClass调用都将解析用于加载共享库的类加载器上下文中的类。当从其他上下文调用时,FindClass使用与 Java 堆栈顶部的方法关联的类加载器,或者如果没有(因为调用来自刚刚附加的原生线程),则使用“系统”类加载器。系统类加载器不知道应用程序的类,因此您将无法在该上下文中使用FindClass查找自己的类。这使得JNI_OnLoad成为查找和缓存类的一个方便的地方:一旦你有了一个有效的jclass全局引用,你就可以从任何连接的线程中使用它。

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

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

相关文章

React--》如何高效管理前端环境变量:开发与生产环境配置详解

在前端开发中&#xff0c;如何让项目在不同环境下表现得更为灵活与高效&#xff0c;是每个开发者必须面对的挑战&#xff0c;从开发阶段的调试到生产环境的优化&#xff0c;环境变量配置无疑是其中的关键。 env配置文件&#xff1a;通常用于管理项目的环境变量&#xff0c;环境…

SpringSecurity+jwt+captcha登录认证授权总结

SpringSecurityjwtcaptcha登录认证授权总结 版本信息&#xff1a; springboot 3.2.0、springSecurity 6.2.0、mybatis-plus 3.5.5 认证授权思路和流程&#xff1a; 未携带token&#xff0c;访问登录接口&#xff1a; 1、用户登录携带账号密码 2、请求到达自定义Filter&am…

计算机视觉和机器人技术中的下一个标记预测与视频扩散相结合

一种新方法可以训练神经网络对损坏的数据进行分类&#xff0c;同时预测下一步操作。 它可以为机器人制定灵活的计划&#xff0c;生成高质量的视频&#xff0c;并帮助人工智能代理导航数字环境。 Diffusion Forcing 方法可以对嘈杂的数据进行分类&#xff0c;并可靠地预测任务的…

2024-11-17 -MATLAB三维绘图简单实例

1. x -1:0.05:1; y x; [X, Y] meshgrid(x, y); f (X, Y) (sin(pi * X) .* sin(pi * Y)) .^ 2.*sin(2.*X2.*Y); mesh(X, Y, f(X, Y)); % 调用函数f并传递X和Y xlabel(X-axis); ylabel(Y-axis); zlabel(Z-axis); title(Surface Plot of (sin(pi * X) .* sin(pi * Y)) .^ 2.*…

WebAssembly在桌面级应用开发中的探索与实践

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 WebAssembly在桌面级应用开发中的探索与实践 WebAssembly在桌面级应用开发中的探索与实践 WebAssembly在桌面级应用开发中的探索…

第二十一周学习周报

目录 摘要Abstract1. LSTM原理2. LSTM反向传播的数学推导3. LSTM模型训练实战总结 摘要 本周的学习内容是对LSTM相关内容的复习&#xff0c;LSTM被设计用来解决标准RNN在处理长序列数据时遇到的梯度消失和梯度爆炸问题。LSTM通过引入门控机制来控制信息的流动&#xff0c;从而…

《Spring 基础之 IoC 与 DI 入门指南》

一、IoC 与 DI 概念引入 Spring 的 IoC&#xff08;控制反转&#xff09;和 DI&#xff08;依赖注入&#xff09;在 Java 开发中扮演着至关重要的角色&#xff0c;是提升代码质量和可维护性的关键技术。 &#xff08;一&#xff09;IoC 的含义及作用 IoC 全称为 Inversion of…

Vulnhub靶场案例渗透[9]- HackableIII

文章目录 一、靶场搭建1. 靶场描述2. 下载靶机环境3. 靶场搭建 二、渗透靶场1. 确定靶机IP2. 探测靶场开放端口及对应服务3. 扫描网络目录结构4. 敏感数据获取5. 获取shell6. 提权6.1 敏感信息获取6.2 lxd提权 一、靶场搭建 1. 靶场描述 Focus on general concepts about CTF…

抖音热门素材去哪找?优质抖音视频素材网站推荐!

是不是和我一样&#xff0c;刷抖音刷到停不下来&#xff1f;越来越多的朋友希望在抖音上创作出爆款视频&#xff0c;但苦于没有好素材。今天就来推荐几个超级实用的抖音视频素材网站&#xff0c;让你的视频内容立刻变得高大上&#xff01;这篇满是干货&#xff0c;直接上重点&a…

如何轻松导出所有 WordPress URL 为纯文本格式

作为一名多年的 WordPress 使用者&#xff0c;我深知管理一个网站的复杂性。从迁移网站、设置重定向到整理内容结构&#xff0c;每一步都需要精细处理。而拥有所有 URL 的清单&#xff0c;不仅能让这些工作变得更加简单&#xff0c;还能为后续的管理提供极大的便利。其实&#…

vue项目使用eslint+prettier管理项目格式化

代码格式化、规范化说明 使用eslintprettier进行格式化&#xff0c;vscode中需要安装插件ESLint、Prettier - Code formatter&#xff0c;且格式化程序选择为后者&#xff08;vue文件、js文件要分别设置&#xff09; 对于eslint规则&#xff0c;在格式化时不会全部自动调整&…

Ubuntu 18.04 配置sources.list源文件(无法安全地用该源进行更新,所以默认禁用该源)

如果你 sudo apt update 时出现诸如 无法安全地用该源进行更新&#xff0c;所以默认禁用该源 的错误&#xff0c;那就换换源吧&#xff0c;链接&#xff1a; https://mirror.tuna.tsinghua.edu.cn/help/ubuntu/ 注意版本&#xff1a; 修改源文件&#xff1a; sudo nano /etc…

5. langgraph中的react agent使用 (从零构建一个react agent)

1. 定义 Agent 状态 首先&#xff0c;我们需要定义 Agent 的状态&#xff0c;这包括 Agent 所持有的消息。 from typing import (Annotated,Sequence,TypedDict, ) from langchain_core.messages import BaseMessage from langgraph.graph.message import add_messagesclass …

【网络】什么是交换机?switch

交换机&#xff08;Switch&#xff09;意为“开关”&#xff0c;是一种用于电&#xff08;光&#xff09;信号转发的网络设备。以下是关于交换机的详细解释&#xff1a; 一、交换机的基本定义 功能&#xff1a;交换机能为接入交换机的任意两个网络节点提供独享的电信号通路&am…

【AlphaFold3】开源本地的安装及使用

文章目录 安装安装DockerInstalling Docker on Host启用Rootless Docker 安装 GPU 支持安装 NVIDIA 驱动程序安装 NVIDIA 对 Docker 的支持 获取 AlphaFold 3 源代码获取基因数据库获取模型参数构建将运行 AlphaFold 3 的 Docker 容器 参考 AlphaFold3: https://github.com/goo…

【免越狱】iOS砸壳 可下载AppStore任意版本 旧版本IPA下载

软件介绍 下载iOS旧版应用&#xff0c;简化繁琐的抓包流程。 一键生成去更新IPA&#xff08;手机安装后&#xff0c;去除App Store的更新检测&#xff09;。 软件界面 支持系统 Windows 10/Windows 8/Windows 7&#xff08;由于使用了Fiddler库&#xff0c;因此需要.Net环境…

shell 100例

1、每天写一个文件 (题目要求&#xff09; 请按照这样的日期格式(xxxx-xx-xx每日生成一个文件 例如生成的文件为2017-12-20.log&#xff0c;并且把磁盘的使用情况写到到这个文件中不用考虑cron&#xff0c;仅仅写脚本即可 [核心要点] date命令用法 df命令 知识补充&#xff1…

Acrobat Pro DC 2023(pdf免费转化word)

所在位置 通过网盘分享的文件&#xff1a;Acrobat Pro DC 2023(64bit).tar 链接: https://pan.baidu.com/s/1_m8TT1rHTtp5YnU8F0QGXQ 提取码: 1234 --来自百度网盘超级会员v4的分享 安装流程 打开安装所在位置 进入安装程序 找到安装程序 进入后点击自定义安装&#xff0c;这里…

linux之调度管理(5)-实时调度器

一、概述 在Linux内核中&#xff0c;实时进程总是比普通进程的优先级要高&#xff0c;实时进程的调度是由Real Time Scheduler(RT调度器)来管理&#xff0c;而普通进程由CFS调度器来管理。 实时进程支持的调度策略为&#xff1a;SCHED_FIFO和SCHED_RR。 SCHED_FIFO&#xff…

在arm64架构下, Ubuntu 18.04.5 LTS 用命令安装和卸载qt4、qt5

问题&#xff1a;需要在 arm64下安装Qt&#xff0c;QT源码编译失败以后&#xff0c;选择在线安装&#xff01; 最后安装的版本是Qt5.9.5 和QtCreator 4.5.2 。 一、ubuntu安装qt4的命令(亲测有效)&#xff1a; sudo add-apt-repository ppa:rock-core/qt4 sudo apt updat…