Android 源码中的 JNI,到底是如何使用的?
ztj100 2024-12-06 20:56 27 浏览 0 评论
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.
*/
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
/** @hide */ public static final int LOG_ID_RADIO = 1;
/** @hide */ public static final int LOG_ID_EVENTS = 2;
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static native int println_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 的定义:
typedef struct {
const char* name; //java层声明的native函数的函数名
const char* 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*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(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;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus) //定义了C++
typedef _JNIEnv JNIEnv; //C++中的JNIEnv的类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* 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); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(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 文档(知识脉络 + 诸多细节)有需要的小伙伴:可以私信发送 “脑图” 或 "笔记"就可以免费领取了
音视频开发思维导图
好了,以上就是今天要分享的内容,大家觉得有用的话,可以点赞分享一下;如果文章中有什么问题欢迎大家指正;欢迎在评论区或后台讨论哈~
相关推荐
- 离谱!写了5年Vue,还不会自动化测试?
-
前言大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。Playwright是一个功能强大的端到...
- package.json 与 package-lock.json 的关系
-
模块化开发在前端越来越流行,使用node和npm可以很方便的下载管理项目所需的依赖模块。package.json用来描述项目及项目所依赖的模块信息。那package-lock.json和...
- Github 标星35k 的 SpringBoot整合acvtiviti开源分享,看完献上膝盖
-
前言activiti是目前比较流行的工作流框架,但是activiti学起来还是费劲,还是有点难度的,如何整合在线编辑器,如何和业务表单绑定,如何和系统权限绑定,这些问题都是要考虑到的,不是说纯粹的把a...
- Vue3 + TypeScript 前端研发模板仓库
-
我们把这个Vue3+TypeScript前端研发模板仓库的初始化脚本一次性补全到可直接运行的状态,包括:完整的目录结构所有配置文件研发规范文档示例功能模块(ExampleFeature)...
- Vue 2迁移Vue 3:从响应式到性能优化
-
小伙伴们注意啦!Vue2已经在2023年底正式停止维护,再不升级就要面临安全漏洞没人管的风险啦!而且Vue3带来的性能提升可不是一点点——渲染速度快40%,内存占用少一半,更新速度直接翻倍!还在...
- VUE学习笔记:声明式渲染详解,对比WEB与VUE
-
声明式渲染是指使用简洁的模板语法,声明式的方式将数据渲染进DOM系统。声明式是相对于编程式而言,声明式是面向对象的,告诉框架做什么,具体操作由框架完成。编程式是面向过程思想,需要手动编写代码完成具...
- 苏州web前端培训班, 苏州哪里有web前端工程师培训
-
前端+HTML5德学习内容:第一阶段:前端页面重构:PC端网站布局、HTML5+CSS3基础项目、WebAPP页面布局;第二阶段:高级程序设计:原生交互功能开发、面向对象开发与ES5/ES6、工具库...
- 跟我一起开发微信小程序——扩展组件的代码提示补全
-
用户自定义代码块步骤:1.HBuilderX中工具栏:工具-代码块设置-vue代码块2.通过“1”步骤打开设置文件...
- JimuReport 积木报表 v1.9.3发布,免费可视化报表
-
项目介绍积木报表JimuReport,是一款免费的数据可视化报表,含报表、大屏和仪表盘,像搭建积木一样完全在线设计!功能涵盖:数据报表、打印设计、图表报表、门户设计、大屏设计等!...
- 软开企服开源的无忧企业文档(V2.1.3)产品说明书
-
目录1....
- 一款面向 AI 的下一代富文本编辑器,已开源
-
简介AiEditor是一个面向AI的下一代富文本编辑器。开箱即用、支持所有前端框架、支持Markdown书写模式什么是AiEditor?AiEditor是一个面向AI的下一代富文本编辑...
- 玩转Markdown(2)——抽象语法树的提取与操纵
-
上一篇玩转Markdown——数据的分离存储与组件的原生渲染发布,转眼已经鸽了大半年了。最近在操纵mdast生成md文件的时候,心血来潮,把玩转Markdown(2)给补上了。...
- DeepseekR1+ollama+dify1.0.0搭建企业/个人知识库(入门避坑版)
-
找了网上的视频和相关文档看了之后,可能由于版本不对或文档格式不对,很容易走弯路,看完这一章,可以让你少踩三天的坑。步骤和注意事项我一一列出来:1,前提条件是在你的电脑上已配置好ollama,dify1...
- 升级JDK17的理由,核心是降低GC时间
-
升级前后对比升级方法...
- 一个vsCode格式化插件_vscode格式化插件缩进量
-
ESlint...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)