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函数来设置它的值;调用函数RegisterNativesUnregisterNatives可以注册和反注册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方法。

 

原创粉丝点击