JNI注册调用源码分析完整过程-安卓4.4
来源:互联网 发布:淘宝买家数据 编辑:程序博客网 时间:2024/06/06 10:00
在Android系统中,JNI方法是以C/C++语言来实现的,然后编译在一个so文件里面,以我之前的例子为例Android Studio使用JNI,调用之前要加载到当前应用程序的进程的地址空间中:
static{System.loadLibrary("JniTest");}private native int Add(double num1,double num2);private native int Sub(double num1,double num2);private native int Mul(double num1,double num2);private native int Div(double num1,double num2);
上述方法假设类com.example.caculate有4个方法Add,Sub,Mul,Div是现在libJniTest.so文件中,因此JNI方法被调用之前我们首先要将它加载到当前的应用程序进程中来,通过调用System类的静态成员函数loadLibrary来实现的。
JNI方法的注册我们查看动态注册的代码:
#include <jni.h>#include <stdio.h>//#include <assert.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>JNIEXPORT jint JNICALL native_Add(JNIEnv *env, jobject obj, jdouble num1, jdouble num2){return (jint)(num1 + num2 +1);}JNIEXPORT jint JNICALL native_Sub (JNIEnv *env, jobject obj, jdouble num1, jdouble num2){ return (jint)(num1 - num2 +1);}JNIEXPORT jint JNICALL native_Mul (JNIEnv *env, jobject obj, jdouble num1, jdouble num2){ return (jint)(num1 * num2 +1);}JNIEXPORT jint JNICALL native_Div (JNIEnv *env, jobject obj, jdouble num1, jdouble num2){ if (num2 == 0) return 0; return (jint)(num1 / num2 +1);}//Java和JNI函数的绑定表static JNINativeMethod gMethods[] = { {"Add", "(DD)I", (void *)native_Add}, {"Sub", "(DD)I", (void *)native_Sub}, {"Mul", "(DD)I", (void *)native_Mul}, {"Div", "(DD)I", (void *)native_Div},};//注册native方法到java中static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods){ jclass clazz; clazz = (*env)->FindClass(env, className);
if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods,numMethods) < 0){ return JNI_FALSE; } return JNI_TRUE;}int register_ndk_load(JNIEnv *env){ return registerNativeMethods(env, "com/example/caculate/MainActivity", gMethods,sizeof(gMethods) / sizeof(gMethods[0])); //NELEM(gMethods));}JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { //获得env return result; } register_ndk_load(env); // 返回jni的版本 return JNI_VERSION_1_4;}
经过上述的编码,编译就能生成libJniTest.so文件。
当libJniTest.so文件被加载的时候,函数JNI_OnLoad就会被调用。在函数JNI_Onload中,参数vm是当前进程中的Dalvik虚拟机,通过调用它的成员函数GetEnv就可以获得一个JNIEnv对象。其中JNIEnv是Dalvik虚拟机实例中的一个JNI环境列表,JNIEnv中有个成员变量指向本地接口表JNINativeInterface,当我们在C/C++代码中调用Java函数,就需要用到这个本地接口表,例如调用FindClass找到指定的Java类;调用GetMethodId可以获得一个Java类成员函数,并且可以通过类似CallObjectMethod函数来设置它的值;调用函数RegisterNatives和UnregisterNatives可以注册和反注册JNI方法到一个Java类中,便可以在Java函数中调用;调用GetJavaVM可以获得当前进程中的Dalvik虚拟机实例。每一个关联有JNI环境的线程都有一个对应的JNIEnv对象,JNIEnv是一个双向链表结构,其中第一个是主线程的JNIEnv对象。
之后我们便通过调用JNIEnv对象中接口函数FindClass函数获得当前JNIEnv对象关联的类名
clazz = (*env)->FindClass(env, className);
然后通过JNIEnv对象中接口函数RegisterNatives注册我们的4个方法。
(*env)->RegisterNatives(env, clazz, gMethods,numMethods)
以下分析在Android 4.4源码中
一、我们重点分析JNI的注册过程:
1.我们通过System.loadLibrary()函数加载我们的动态库。我们查看loadLibrary函数。
得到调用过程为
System.loadlibrary() //在System.java中,Java检查是否能被加载,调用Runtime.loadLibrary
|_Runtime.loadLibrary() //在Runtime.java中,检查路径,获得so绝对路径,路径合法,调用Runtime.nativeLoad
|_Runtime.nativeLoad() //JNI方法,Dalvik虚拟机在启动过程中注册的Java核心类操作
|| //在java_lang_Runtime.c中
Dalvik_java_lang_Runtime_nativeLoad()//将java层的String对象转换成C++层字符串,再调用 dvmLoadNativeCode来执行so文件的加载操作
|_dvmLoadNativeCode()
dvmLoadNativeCode()函数检查so文件是否已经加载过了,就直接返回so文件的加载信息,如果没有加载,调用dlopen加载到进程中,创建SharedLib对象pNewEntry来描述加载信息,调用dlsym获得JNI_OnLoad函数在so文件中的函数指针,然后调用这个函数指针,传入JavaVM对象,这个JAVAVM对象描述了当前进程中运行的Dalvik虚拟机。
这样在调用System.loadLibrary()函数,so文件中的JNI_Onload函数就执行了,并且第一个参数描述了当前进程的Dalvik虚拟机对象。
2.JNI_Onload函数注册JNI函数。
(*vm)->GetEnv() //获得Dalvik虚拟机关联的JNIEnv对象(当前线程的JNI环境)
(*env)->FindClass() //JNIEnv结构体中的本地接口表中函数
(*env)->RegisterNatives() //JNIEnv结构体中的本地接口表中函数
||
RegisterNatives()
|_dvmDecodeIndirectRef() //获得要注册JNI方法的类的对象引用clazz
|_dvmRegisterJNIMethod() //通过循环调用此函数,注册methods描述的每一个JNI方法
|_dvmFindDirectMethodByDescriptor //检查method方法是否是clazz的的非虚成员函数
|_dvmFindVirtualMethodByDescriptor //检查method方法是否是clazz的虚成员函数
|_dvmInNativeMethod //确保clazz的成员函数method声明为JNI方法
|_dvmIsSynchronizedMethod //是否是同步的
|_dvmIsStaticMethod //是否是静态方法
|_dvmResolveNativeMethod //检查Dalvik虚拟机内部以及当前所有加载的共享库中是否存在对应的JNI方法
|_dvmUseJNIBridge
|_Bridge=dvmCheckCallJNIMethod / dvmCallJNIMethod //根据虚拟机设置,设置Bridge函数
|_dvmSetNativeFunc 将bridge函数地址和JNI函数地址放入需要注册的JNI的method中
|_method->insns = insns; //JNI方法的函数地址
|_android_atomic_release_store((int32_t) func,
(volatile int32_t*)(void*) &method->nativeFunc); //将dvmCallMethodV函数放入method->nativeFunc中,后面直接通过该函数调用JNI方法的函数地址
参数method表示要注册JNI方法的Java类成员函数,参数func表示JNI方法的Bridge函数(dvmCallJNIMethod或者dvmCheckCallJNIMethod),参数insns表示要注册的JNI方法的函数地址。
当参数insns的值不等于NULL的时候,函数dvmSetNativeFunc就分别将参数insns和func的值分别保存在参数method所指向的一个Method对象的成员变量insns和nativeFunc中,而当insns的值等于NULL的时候,函数dvmSetNativeFunc就只将参数func的值保存在参数method所指向的一个Method对象成员变量nativeFunc中。
到此注册过程已经完成。
二、我们再看看函数的调用过程:
我们通过CallVoidMethodV系列函数调用其他方法,这些函数是JNIEnv结构体中本地接口表中的函数。
CallVoidMethodV
|_dvmCallMethodV (stack.c中)
|_dvmIsNativeMethod() 是JNI方法
| |_(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,method, self); //使用之前注册是的bridge函数:dvmCallJNIMethod函数
|_dvmInterpret (Interp.c中) 是Java方法
|_dvmMterpStd JIT和fast模式
|_dvmInterpretPortable Portable可移植模式 (在InterpC-portstd.c中)
|_1.初始化当前要解释的类(methodClassDex)及其成员变量函数(curMethod)、栈帧(fp)、程序计数器(pc)和返回值(retval),这些值都可以从参数interpState获得。
|_2.再一个无限while循环中,通过FETCH宏依次获得当前程序计数器(pc)的指令,并通过宏INST_INST获得指令inst的类型,最后就switch到对应的分支去解释指令inst。
其中dvmCheckCallJNIMethod检查JNI之后调用dvmCallJNIMethod函数(jni.c中)
调用之前注册JNI时指定的dvmCallJNIMethod函数,通过这个函数执行JNI方法
dvmCallJNIMethod
|_addLocalReference(thread,method->clazz) 增加线程对java类对象的引用
|_dvmChangeStatus(thread,THREAD_NATIVE) 更改线程状态为native模式
|_dvmPlatformInvoke() //函数通过libffi库来调用对应的JNI方法,来屏蔽Dalvik虚拟机运行在不同目标平台的细节
|_dvmChangeStatus() 恢复线程状态
JNI环境对象结构体:
struct JNIEnvExt { //JNI环境对象 const struct JNINativeInterface* funcTable; /* must be first */ const struct JNINativeInterface* baseFuncTable; u4 envThreadId; Thread* self; /* if nonzero, we are in a "critical" JNI call */ int critical; struct JNIEnvExt* prev; struct JNIEnvExt* next;};
JNINativeInterface: JNIEnv的回调函数表
View Code
总结一下:
系统注册或者自己注册的JNI,通过JNIEnv对象中的本地接口表函数RegisterNative注册JNI方法,使用其中的CallVoidMethod系列函数调用JNI方法。
阅读全文
0 0
- JNI注册调用源码分析完整过程-安卓4.4
- JNI注册调用完整过程-安卓4.4
- Android服务注册完整过程源码分析
- Android服务注册完整过程源码分析
- android 动态注册JNI函数过程源码分析
- 利用安卓源码的例子程序编写调用JNI
- 安卓JNI分析
- 安卓JNI分析
- java jni调用过程分析
- JNI源码分析 (并实现JNI动态注册)
- JNI源码分析 (并实现JNI动态注册)
- JNI源码分析(并实现JNI动态注册)
- JNI源码分析(并实现JNI动态注册)
- Qt安卓JNI交互之(1) C++注册函数给JAVA调用
- JNI实现源码分析【四 函数调用】
- Android服务查询完整过程源码分析
- Android服务查询完整过程源码分析
- struts2源码过程调用分析
- redis-desktop-manager-0.8.3 for mac
- 如何配置Apache Solr6.6版本
- ffmpeg解码音频的两种方式(二)根据同步字节解析音频帧
- Redis cluster multi-key operation
- JNI开发基础篇:C语言调用Java中的方法
- JNI注册调用源码分析完整过程-安卓4.4
- Eclipse打包为jar的两种方法
- 给初学者的RxJava2.0教程(一)
- 自定义相册获取所有照片,不卡顿
- Faster-rcnn数据准备过程
- 使用Mybatis框架逆向工程
- 关于ckfinder+ckeditor 图片上传路径问题
- 如何使用ABBYY FineReader 14添加文本到PDF文档
- Linux常见命令