JNI注册调用源码分析完整过程-安卓4.4

在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方法。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值