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

Android 源码中的 JNI,到底是如何使用的?

Linux下 JNI的使用

学习 Android 其中涉及对 JNI 的使用;JNI的使用对于 Android 来说又是十分的重要和关键。那么到底 Java 到底是如何调用 C/C++ 的,

下面是非常简单的计算器源码,只是用来熟悉JNI的基本语法,其中我自己碰到过的一个问题

就是LoadLibrary()调用之后,程序直接崩溃,最开始以为是模拟器是x86的模式,而编译的so文件是arm的模式,但是将模拟器改成arm之后还是崩溃,最后无奈在自己手机上测试也是如此,一打开就直接崩溃,在网上能找到的各种方法都试了,最后发现是so命名的问题

我们经常会写如下的代码输出日志:

Log.d(TAG,”Debug Log”);

我们就以Log系统为例来学习JNI。

我们先看一下Log类的内容,在android源码的\frameworks\base\core\java\android\Log.java文件中

/*** Send a {@link #DEBUG} log message.* @param tag Used to identify the source of a log message.  It usually identifies*        the class or activity where the log call occurs.* @param msg The message you would like logged.*/publicstaticintd(String tag, String msg) {returnprintln_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
​
​/** @hide */publicstaticfinalint LOG_ID_MAIN = 0;/** @hide */publicstaticfinalint LOG_ID_RADIO = 1;/** @hide */publicstaticfinalint LOG_ID_EVENTS = 2;/** @hide */publicstaticfinalint LOG_ID_SYSTEM = 3;
​/** @hide */publicstatic native intprintln_native(int bufID,int priority, String tag, String msg);
复制代码

可以看到所有的Log的方法都调用了native 的println_native方法,在android源码中的\frameworks\base\core\jni\android_until_Log.cpp文件中实现:

/** In class android.util.Log:*  public static native int println_native(int buffer, int priority, String tag, String msg)*//*
*JNI方法增加了JNIEnv和jobject两参数,其余的参数和返回值只是将Java层参数映**射成JNI的数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,最后返给java层
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,jint bufID, jint priority, jstring tagObj, jstring msgObj)
{const char* tag = NULL;const char* msg = NULL;
​if (msgObj == NULL) {   //异常处理jclass npeClazz;
​npeClazz = env->FindClass("java/lang/NullPointerException");assert(npeClazz != NULL);//抛出异常env->ThrowNew(npeClazz, "println needs a message");return -1;}
​if (bufID < 0 || bufID >= LOG_ID_MAX) {jclass npeClazz;
​npeClazz = env->FindClass("java/lang/NullPointerException");assert(npeClazz != NULL);
​env->ThrowNew(npeClazz, "bad bufID");return -1;}
​if (tagObj != NULL)tag = env->GetStringUTFChars(tagObj, NULL);msg = env->GetStringUTFChars(msgObj, NULL);//向内核写入日志int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
​if (tag != NULL)env->ReleaseStringUTFChars(tagObj, tag);env->ReleaseStringUTFChars(msgObj, msg);
​return res;
}
复制代码

至此,JNI层已经实现了在java层声明的Native层方法,但是这两个又是如何联系到一起的呢?我们再看android_util_Log.cpp的源码

/** JNI registration.*/static JNINativeMethod gMethods[] = {/* name, signature, funcPtr */{ "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },{"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native },
};
复制代码

在\dalvik\libnativehelper\include\nativehelper\Jni.h文件中有JNINativeMethod 的定义:

typedefstruct {constchar* name;        //java层声明的native函数的函数名constchar* signature;   //Java函数的签名void*       fnPtr;       //函数指针,指向JNI层的实现方法
} JNINativeMethod;
复制代码

我们可以看到printIn_native的对应关系:

{"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native }
复制代码

Java层声明的函数名是print_native

Java层声明的native函数的签名为(IILjava/lang/String;Ljava/lang/String;)I

JNI方法实现方法的指针为(void*)android_util_Log_println_native

我们知道了java层和JNI层的映射关系,但是如何把这种关系告诉Dalvik虚拟机呢?,我们继续看android_util_Log.cpp的源码

int register_android_util_Log(JNIEnv* env)
{jclass clazz = env->FindClass("android/util/Log");
​if (clazz == NULL) {LOGE("Can't find android/util/Log");return -1;}levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}
​
}; // namespace android复制代码

这个函数的最后调用了AndroidRuntime::registerNativeMethods函数

可以在\frameworks\base\core\jni\AndroidRuntime.cpp 中找到registerNativeMethods的实现

/** Register native methods using JNI.*//*static*/intAndroidRuntime::registerNativeMethods(JNIEnv* env,constchar* className, const JNINativeMethod* gMethods, int numMethods){returnjniRegisterNativeMethods(env, className, gMethods, numMethods);
}
复制代码

他的内部实现只是调用了jniRegisterNativeMethods ()。

在\dalvik\libnativehelper\JNIHelp.c中jniRegisterNativeMethods函数的实现

/** Register native JNI-callable methods.** "className" looks like "java/lang/String".*/
int jniRegisterNativeMethods(JNIEnv* env, const char* className,const JNINativeMethod* gMethods, int numMethods)
{jclass clazz;
​LOGV("Registering %s natives\n", className);clazz = (*env)->FindClass(env, className);if (clazz == NULL) {LOGE("Native registration unable to find class '%s'\n", className);return -1;}
​int result = 0;if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {LOGE("RegisterNatives failed for '%s'\n", className);result = -1;}
​(*env)->DeleteLocalRef(env, clazz);return result;
}
复制代码

这里是调用了JNIEnv的RegisterNatives函数,可以阅读函数的注释,注册一个类的Native方法。已经告诉了虚拟机java层和native层的映射关系。

/** Register one or more native functions in one class.** This can be called multiple times on the same method, allowing the* caller to redefine the method implementation at will.*/
static jint RegisterNatives(JNIEnv* env, jclass jclazz,const JNINativeMethod* methods, jint nMethods)
{JNI_ENTER();
​ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);jint retval = JNI_OK;int i;
​if (gDvm.verboseJni) {LOGI("[Registering JNI native methods for class %s]\n",clazz->descriptor);}
​for (i = 0; i < nMethods; i++) {if (!dvmRegisterJNIMethod(clazz, methods[i].name,methods[i].signature, methods[i].fnPtr)){retval = JNI_ERR;}}
​JNI_EXIT();return retval;
}
复制代码

其作用是向clazz参数指定的类注册本地方法,这样,虚拟机就能得到Java层和JNI层之间的对应关系,就可以实现java和native层代码的交互了。我们注意到在Log系统的实例中,JNI层实现方法和注册方法中都使用了JNIEnv这个指针,通过它调用JNI函数,访问Dalvik虚拟机,进而操作Java对象

我们可以在\Dalvik\libnativehelper\include\nativehelper\jni.h中找到JNIEnv的定义:

struct_JNIEnv;
struct_JavaVM;
typedefconststructJNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)   //定义了C++typedef _JNIEnv JNIEnv;   //C++中的JNIEnv的类型typedef _JavaVM JavaVM;
#elsetypedefconststructJNINativeInterface* JNIEnv;
typedefconststructJNIInvokeInterface* JavaVM;
#endif复制代码

这里只是用关键字typedef关键字做了类型定义,那么_JNIEnv和JNINativeInterface的定义

/** C++ object wrapper.** This is usually overlaid on a C struct whose first element is a* JNINativeInterface*.  We rely somewhat on compiler behavior.*/
struct _JNIEnv {/* do not rename this; it does not seem to be entirely opaque */const struct JNINativeInterface* functions;
​
#if defined(__cplusplus)
​jint GetVersion(){ return functions->GetVersion(this); }
​jclassDefineClass(const char *name, jobject loader, const jbyte* buf,jsize bufLen){ return functions->DefineClass(this, name, loader, buf, bufLen); }
​jclassFindClass(const char* name){ return functions->FindClass(this, name); }
​jmethodID FromReflectedMethod(jobject method){ return functions->FromReflectedMethod(this, method); }
​………..
复制代码

_JNIEnv只是对const struct JNINativeInterface类型的封装,并间接调用const struct JNINativeInterface上定义的方法

/** Table of interface function pointers.*/
struct JNINativeInterface {
……jclass      (*FindClass)(JNIEnv*, const char*);jboolean    (*IsSameObject)(JNIEnv*, jobject, jobject);
……
};
复制代码

这里才真正涉及JNI函数的调用,也只是一个接口

但是我们可以得出如下结论:

C++中: JNIEnv就是struct _JNIEnv。JNIEnv *env 等价于 struct _JNIEnv env ,在调用JNI函数的时候,只需要env->FindClass(JNIEnv,const char ),就会间接调用JNINativeInterface结构体里面定义的函数指针,而无需首先对env解引用。

C中: JNIEnv就是const struct JNINativeInterface *。JNIEnv env 等价于const struct JNINativeInterface ** env,因此要得到JNINativeInterface结构体里面的函数指针就必须先对env解引用得到( env),得到const struct JNINativeInterface *,才是真正指向JNINativeInterface结构体的指针,然后再通过它调用具体的JNI函数,因此需要这样调用:

(env)->FindClass(JNIEnv,const char*)。

尾述

最后这里放上一张大佬推荐的 音视频开发 的脑图,并根据脑图整理了一份系统学习的资料笔记和配套视频音视频开发技术相关的知识点笔记中都有详细的解读,并且把每个技术点整理成了 PDF 文档(知识脉络 + 诸多细节)有需要的小伙伴点击文末的卡片或者【点击这里】

相关文章:

Android 源码中的 JNI,到底是如何使用的?

Linux下 JNI的使用学习 Android 其中涉及对 JNI 的使用&#xff1b;JNI的使用对于 Android 来说又是十分的重要和关键。那么到底 Java 到底是如何调用 C/C 的&#xff0c;下面是非常简单的计算器源码&#xff0c;只是用来熟悉JNI的基本语法&#xff0c;其中我自己碰到过的一个问…...

重磅新品 / 酷炫展品 / 强大生态,广和通玩转 MWC Barcelona 2023

2月27日&#xff0c;2023世界移动通信大会&#xff08;MWC Barcelona 2023&#xff09;在西班牙巴塞罗那正式开幕。全球知名移动运营商、设备制造商、技术提供商、物联网企业齐聚一堂&#xff0c;以领先的技术、创新的场景、前瞻的洞察向全行业输送最新鲜的行业观点。作为全球领…...

Hbuilder+uniapp 从零开始创建一个小程序

当你看到这篇博客的时候&#xff0c;那~说明~我的这篇博客写完了……哈哈哈哈哈哈哈哈。好的&#xff0c;清耐心往下看哈。如果有需要的&#xff0c;可以关注一下小作&#xff0c;后面还有小程序的云开发嗷~一、申请一个小程序账号&#xff08;已经有账号的小可爱可以跳过&…...

亚商投资顾问早餐FM/0303支持新能源汽车消费

01/亚商投资顾问早间导读高层调研集成电路企业并主持召开座谈会商务部&#xff1a;今年将积极出台新政策措施支持新能源汽车消费商务部&#xff1a;推动农村消费进一步恢复和扩大更好助力乡村振兴干细胞应用接连获重大突破机构密集调研相关上市公司02/亚商投资顾问新闻早餐// 热…...

Spring Boot 整合分布式缓存 Memcached

Memcached是一个开源、高性能&#xff0c;将数据分布于内存中并使用key-value存储结构的缓存系统。它通过在内存中缓存数据来减少向数据库的频繁访问连接的次数&#xff0c;可以提高动态、数据库驱动之类网站的运行速度。 Memcached在使用是比较简单的&#xff0c;在操作上基本…...

嵌入式学习笔记——STM32单片机开发前的准备

STM32单片机开发前的准备1.集成开发环境的选取STM32 CubeIDEKEIL_MDK2.KEIL_MDK环境搭建安装包获取及安装芯片包下载及安装工程建立(STM32F407VET6为例)1.新建工程文件夹2.新建工程3.安装ST-LINK以及CH340的驱动4.设置KEIL&#xff0c;并烧录本文重点1.集成开发环境的选取 前面…...

客户案例|FPGA研发管理解决方案:UniPro瀑布+敏捷 打造高效能组织

2023开年以来&#xff0c;新享科技项目管理软件UniPro收获一波客户侧的点赞好评。在过去一年中&#xff0c;UniPro不断与客户保持高频沟通&#xff0c;满足客户需求为出发点&#xff0c;以产品功能实现为落脚点&#xff0c;不断打磨产品。 以UniPro客户京微齐力为例&#xff0…...

【信息学奥赛】1400:统计单词数

统计单词数也需要分割单词&#xff0c;如果使用字符数组来做的话&#xff0c;其实和1144&#xff1a;单词翻转类似&#xff0c;但是我一直只能通过四个样例&#xff0c;估计边界处理条件还是有点问题。 不过经过打印字符串长度之后发现了之前遇到的一个问题&#xff0c;即fget…...

# 技术详解: 利用CI同步文章以及多端发布

技术详解: 利用CI同步文章以及多端发布 技术详解: 利用CI同步文章以及多端发布 前言文章的同步实现的细节 思路文章元数据的定义和提取修改文章的优化本地图片资源上传CDN并替换本地link 终于到了 CI 的部分了最后来一些碎碎念 前言 前几天我更新了一篇简单技术总结之后&am…...

分形维数的计算方法汇总

以下是常用的时间序列分形维数计算方法及相应的参考文献&#xff1a;Hurst指数法Hurst指数法是最早用于计算分形维数的方法之一&#xff0c;其基本思想是通过计算时间序列的长程相关性来反映其分形特性。具体步骤是&#xff1a;(1) 对原始时间序列进行标准化处理。(2) 将序列分…...

微积分小课堂:积分(从微观趋势了解宏观变化)

文章目录 引言I. 预备知识: 积分效应1.1 闯黄灯1.2 公司利润(飞轮效应)1.3 飞轮效应II 积分2.1 积分的计算2.2 积分思想的本质引言 微分解决的问题是从宏观变化了解微观趋势;积分和微分刚好相反,是从微观去看宏观变化。 通过积分效应,提升我们的认识水平,同时能用一些工…...

4道数学题,求解极狐GitLab CI 流水线|第4题:合并列车

本文来自&#xff1a; 武让 极狐GitLab 高级解决方案架构师 &#x1f4a1; 极狐GitLab CI 依靠其一体化、轻量化、声明式、开箱即用的特性&#xff0c;在开发者群体中的使用率越来越高&#xff0c;在国内企业中仅次于 Jenkins &#xff0c;排在第二位。 极狐GitLab 流水线有 4…...

代码规范简述

目录 命名规范 代码格式 OOP规约 集合规范 并发规范 SQL语句规范 SQL 建表规范 SQL 索引规范 SQL 查询规范 控制语句规范 Javadoc 规范 其他规范 命名规范 1、包名&#xff1a;使用小写字母&#xff0c;多个单词之间用"."分隔&#xff0c;例如&#xff…...

【Java集合框架】篇五:Map接口

1. Map及实现类特点 Map&#xff1a;存储key-value HashMap&#xff1a;线程不安全&#xff0c;效率高&#xff0c;key和value都可以为null&#xff0c;底层使用 数组单向链表红黑树 结构&#xff08;jdk8&#xff09;。 LinkedHashMap&#xff1a;是HashMap的子类&#xff0…...

Typroa安装教程

Markdown 是一种轻量级标记语言&#xff0c;创始人为约翰格鲁伯&#xff08;John Gruber&#xff09;。 它允许人们使用易读易写的纯文本格式编写文档&#xff0c;然后转换成有效的 XHTML&#xff08;或者HTML&#xff09;文档。这种语言吸收了很多在电子邮件中已有的纯文本标记…...

【MySQL】存储引擎

目录 1.MySQL体系结构 2.存储引擎介绍 3.存储引擎特点 4.存储引擎选择 1.MySQL体系结构 MySQL整体的逻辑结构可以分为4层,客户层、服务层、存储引擎层、数据层 客户层 客户层:进行相关的连接处理、权限控制、安全处理等操作 服务层 服务层负责与客户层进行连接处理、处…...

芯驰(E3-gateway)开发板环境搭建以及调试遇到问题的解决

1-Windows下环境配置 可以在Windows上使用命令行或者IAR IDE编译SSDK项目。Windows编译依赖的工具已经包含在 prebuilts/windows 目录中&#xff0c;包括编译器、Python和命令行工具。 1.1.1 CMD SSDK集成 msys 工具&#xff0c;可以在Windows命令行中完成SDK的配置、编译和…...

【大数据监控】Prometheus、Node_exporter、Graphite_exporter安装部署详细文档

目录Prometheus简介下载软件包安装部署创建用户创建Systemd服务修改配置文件prometheus.yml启动Prometheusnode exporter下载软件包安装部署添加用户创建systemd服务启动node_exportergraphite_exporter下载软件包安装部署创建systemd服务启动 graphite_exporterPrometheus 简介…...

《C++ Primer》 第十一章 关联容器

《C Primer》 第十一章 关联容器 11.1 使用关联容器 使用map: //统计每个单词在输入中出现的次数 map<string, size_t> word_count;//string到size_t的空map string word; while(cin>>word)word_count[word];//提取word的计数器并将其加1 for(const auto &w:…...

WebRTC标准与框架解读(1)

1、如果让我来设计webrtc框架我在分析源码的时候&#xff0c;都喜欢做这样一件事情&#xff1a;如果让我来设计它&#xff0c;我会怎么做&#xff1f;大家可以紧跟我的思路&#xff0c;分析一下WebRTC为什么如此设计。为了对整个框架有有一个全面的了解&#xff0c;我们首先要做…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...