在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方法。
此文为学习JNI注册、调用过程,有错误的地方请大家指出谢谢!