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.mzph.cn/diannao/61033.shtml

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

相关文章

解锁远程AI工作流:Flowise搭配cpolar跨地域管理AI项目

文章目录 前言1. Docker安装Flowise2. Ubuntu安装Cpolar3. 配置Flowise公网地址4. 远程访问Flowise5. 固定Cpolar公网地址6. 固定地址访问 前言 如今&#xff0c;工作流自动化与人工智能的结合已成为提升生产力的重要手段。Flowise正是这样一个工具&#xff0c;通过直观的拖拽…

Cyberchef配合Wireshark提取并解析HTTP/TLS流量数据包中的文件

本文将介绍一种手动的轻量级的方式&#xff0c;还原HTTP/TLS协议中传输的文件&#xff0c;为流量数据包中的文件分析提供帮助。 如果捕获的数据包中存在非文本类文件&#xff0c;例如png,jpg等图片文件&#xff0c;或者word&#xff0c;Excel等office文件异或是其他类型的二进…

MATLAB用CNN-LSTM神经网络的语音情感分类深度学习研究

全文链接&#xff1a;https://tecdat.cn/?p38258 在语音处理领域&#xff0c;对语音情感的分类是一个重要的研究方向。本文将介绍如何通过结合二维卷积神经网络&#xff08;2 - D CNN&#xff09;和长短期记忆网络&#xff08;LSTM&#xff09;构建一个用于语音分类任务的网络…

Android笔记(三十七):封装一个RecyclerView Item曝光工具——用于埋点上报

背景 项目中首页列表页需要统计每个item的曝光情况&#xff0c;给产品运营提供数据报表分析用户行为&#xff0c;于是封装了一个通用的列表Item曝光工具&#xff0c;方便曝光埋点上报 源码分析 核心就是监听RecyclerView的滚动&#xff0c;在滚动状态为SCROLL_STATE_IDLE的时…

使用视频提升应用在 App Store 中的推广效果

App Store 上有485 万个应用和游戏。每个应用开发者都知道&#xff0c;要在如此庞大的市场中脱颖而出&#xff0c;吸引宝贵的用户眼球&#xff0c;是多么困难。 您需要在应用推广游戏中尝试一些不同的东西&#xff0c;那就是视频预览。这些短小的电影奇迹已经成为应用营销人员…

Minikube 上安装 Argo Workflow

文章目录 步骤 1&#xff1a;启动 Minikube 集群步骤 2&#xff1a;安装Argo Workflow步骤 3&#xff1a;访问UI创建流水线任务参考 前提条件&#xff1a; Minikube&#xff1a;确保你已经安装并启动了 Minikube。 kubectl&#xff1a;确保你已经安装并配置了 kubectl&#xff…

关于做完 C# 项目的问题总结

1. .Any()方法使用 可以与其他LINQ方法结合使用&#xff0c;以构建更复杂的查询。例如&#xff0c;你可以首先过滤集合&#xff0c;然后检查过滤后的集合是否包含任何元素&#xff1a; List<string> fruits new List<string> { "Apple", "Banana&q…

Vue实现响应式导航菜单:桌面端导航栏 + 移动端抽屉式菜单

在现代Web开发中&#xff0c;为了提升用户体验&#xff0c;响应式设计已成为必备技能。本文将通过Vue.js&#xff0c;创建一个响应式导航菜单&#xff0c;桌面端显示顶部导航栏&#xff0c;移动端则切换为抽屉式菜单&#xff0c;并具备点击遮罩关闭的功能。以下是具体实现步骤。…

GCP Cloud Storage 的lock retention policy是什么

简介 Google Cloud Storage 的锁定保留策略&#xff08;Lock Retention Policy&#xff09;是一种用于保护存储桶中对象数据的功能。它允许用户设置一个保留期&#xff0c;在此期间对象不能被删除或覆盖。这对于确保数据的长期保留和合规性非常重要&#xff0c;尤其是在需要满…

STM32设计防丢防摔智能行李箱

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展&#xff0c;嵌入式系统、物联网技术、智能设备…

CSP/信奥赛C++语法基础刷题训练(11):洛谷P5743:猴子吃桃

CSP/信奥赛C语法基础刷题训练&#xff08;11&#xff09;&#xff1a;洛谷P5743&#xff1a;猴子吃桃 题目描述 一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半&#xff0c;又贪嘴多吃了一个&#xff1b;接下来的每一天它都会吃剩余的桃子的一半外加一个。第 n n n…

控制器ThinkPHP6

五、控制器中对数组值的返回 在做接口服务时&#xff0c;很多时候回使用数组作为返回值&#xff0c;那么数组如何返回成 json呢&#xff1f; 在 tp6 中返回json 很简单&#xff0c;直接使用 json 进行返回即可&#xff0c;例如&#xff1a; public function index(){$resarra…

洛谷刷题日记||基础篇8

#include <iostream> #include <vector> using namespace std;int N, M; // N为行数&#xff0c;M为列数 vector<vector<char>> field; // 表示田地的网格&#xff0c;每个元素是W或. vector<vector<bool>> visited; // 用来记录网格是否访…

随机森林(Random Forest, RF)筛选回归数据(处理异常值)

下面是一个完整的 MATLAB 代码示例&#xff0c;用于实现随机森林&#xff08;Random Forest, RF&#xff09;回归&#xff0c;执行 5 折交叉验证&#xff0c;并根据预测误差删除误差较大的行&#xff0c;将处理后的数据保存为新的 Excel 文件。 % 导入数据 data readmatrix(d…

Qwen2.5-3B-Instruct-GGUF部署

注册账号&#xff1a; 魔搭社区 等一会&#xff1a; 部署好了&#xff1a; 立即使用&#xff1a; 您部署的服务提供OpenAI API接口&#xff0c;可通过OpenAI SDK进行调用。请确保您的服务处于正常运行状态&#xff0c;并预先安装OpenAI SDK: pip install openai 在本地新建…

微信小程序进行md5加密 ,base64 转码

封装一个Md5加密的工具 &#xff1a; utils /md5.js function md5(string) { function md5_RotateLeft(lValue, iShiftBits) { return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits)); } function md5_AddUnsigned(lX, lY) { var lX4, lY4, l…

服务器虚拟化技术深度解析及代码应用案例

服务器虚拟化技术深度解析及代码应用案例 在现代数据中心和云计算环境中&#xff0c;服务器虚拟化技术已经成为提高资源利用率、降低成本和简化管理的重要手段。本文将详细介绍服务器虚拟化的基本概念、主要类型、技术特性、应用优势&#xff0c;并通过一个基于Golang的容器化…

深入解析Python中的逻辑回归:从入门到精通

引言 在数据科学领域&#xff0c;逻辑回归&#xff08;Logistic Regression&#xff09;是一个非常重要的算法&#xff0c;它不仅用于二分类问题&#xff0c;还可以通过一些技巧扩展到多分类问题。逻辑回归因其简单、高效且易于解释的特点&#xff0c;在金融、医疗、广告等多个…

恶意PDF文档分析记录

0x1 PDF是什么 PDF&#xff08;便携式文件格式&#xff0c;Portable Document Format&#xff09;是由Adobe Systems在1993年用於文件交换所发展出的文件格式。 因为PDF的文件格式性质广泛用于商业办公&#xff0c;引起众多攻击者对其开展技术研究&#xff0c;在一些APT&#…

Spring-事务学习

spring事务 1. 什么是事务? 事务其实是一个并发控制单位&#xff0c;是用户定义的一个操作序列&#xff0c;这些操作要么全部完成&#xff0c;要不全部不完成&#xff0c;是一个不可分割的工作单位。事务有 ACID 四个特性&#xff0c;即&#xff1a; 原子性&#xff08;Atom…