一文深入理解 JNI实现机制

简介:

写在前面

说到JNI都不陌生,它的全称:Java Native Interface,即Java本地接口。
JNI不仅仅是Android特有的,它是属于Java平台的,它允许在Java虚拟机内运行的java代码与其他编程语言(如c, c++和汇编语言)编写的程序和库进行交互。
JNI调用姿势:Java —> JNI —> C/C++(SO库)
在Android平台中,使用JNI封装了跟硬件相关的操作,从而可以通过Java调用相关JNI模块,以达到对硬件的调用。

下面我们将围绕如下内容进行展开:
1.java代码编译和执行过程
2.jni常规使用姿势
3.so加载流程(jni执行流程、注册方式原理)
4.JNIEnv作用及实现

Java代码编译和执行过程

java代码编译和执行的过程涉及到两个命令:javac和java
1.首先通过javac编译java代码,生成class字节码文件
2.javacTestCode.java --> TestCode.class
3.然后通过java命令来执行class字节码
4.javaTestCode.class

java命令执行过程中,会先启动虚拟机,加载TestCode类信息到内存,然后由执行引擎执行其main方法。

JVM的逻辑内存模型如下:

f6ffb00225ed1cf36d9c54c136043cf0cd9cd9b7

JNI常规使用姿势

  1. 在java层通过native关键字创建本地方法,如下:(成员方法、static方法)


public class JNIMethods {

static {

System.loadLibrary("test-jni");

}

//隐式注册

public native String stringFromJNI();

public static native String testParams(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist);



//显式注册

public native String stringFromJNIDis();

public static native String testParamsDis(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist);

}
  1. 创建c层代码,并注册本地方法


#include

#include

#include "common.h"



jstring stringFromJNIDis(JNIEnv *env, jobject jobj);

jstring testParamsDis(JNIEnv *env, jclass type, jint jint_, jstring jstring_, jobject jobject_, jobjectArray objectArray, jobject list);

static jmethodID methodID_toString;

//显式注册

static JNINativeMethod gMethods[] = {

{"stringFromJNIDis", "()Ljava/lang/String;", (void *) stringFromJNIDis},

{"testParamsDis", "(ILjava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;Ljava/util/List;)Ljava/lang/String;", (void *) testParamsDis}

};



static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int nMethods) {

jclass clazz;

clazz = env->FindClass(className);

if (clazz == NULL) {

return JNI_FALSE;

}

if (env->RegisterNatives(clazz, methods, nMethods) < 0) {

return JNI_FALSE;

}

return JNI_TRUE;

}



void appendString(JNIEnv *env, char *dest, jstring tmpStr) {

if (tmpStr == NULL) {

return;

}

const char *tmpString = env->GetStringUTFChars(tmpStr, 0);

strcat(dest, tmpString);

env->ReleaseStringUTFChars(tmpStr, tmpString);

}



//加载so时会执行该方法

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {

LOGD("test-jni.cpp JNI_OnLoad");

JNIEnv *env = NULL;

if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {

return -1;

}

if (NULL == env) {

return -1;

}

jclass clazz = env->FindClass("java/lang/Object");

methodID_toString = env->GetMethodID(clazz, "toString", "()Ljava/lang/String;");

//显式注册本地方法

if (!registerNativeMethods(env, "com/mob/test/jni/testjni/JNIMethods", gMethods, NELEM(gMethods))) {

LOGE("test-jni.cpp JNI_OnLoad register JNIMethods failed");

}

return JNI_VERSION_1_4;

}

//卸载时会执行该方法

JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {

LOGD("test-jni.cpp JNI_OnUnload");

methodID_toString = NULL;

}



//隐式注册

extern "C" JNIEXPORT jstring JNICALL

Java_com_mob_test_jni_testjni_JNIMethods_stringFromJNI(JNIEnv *env, jobject jobj) {

LOGD("test-jni.cpp Java_com_mob_test_jni_testjni_JNIMethods_stringFromJNI");

return stringFromJNIDis(env, jobj);

}



extern "C" JNIEXPORT jstring JNICALL

Java_com_mob_test_jni_testjni_JNIMethods_testParams(JNIEnv *env, jclass type, jint jint_, jstring jstring_, jobject jobject_,

jobjectArray arrayObject,

jobject list) {

LOGD("test-jni.cpp Java_com_mob_test_jni_testjni_JNIMethods_testParams");

return testParamsDis(env, type, jint_, jstring_, jobject_, arrayObject, list);

}



//显式注册

jstring stringFromJNIDis(JNIEnv *env, jobject jobj) {

LOGD("test-jni.cpp stringFromJNIDis");

std::string hello = "Hello from C++";

return env->NewStringUTF(hello.c_str());

}



jstring testParamsDis(JNIEnv *env, jclass type, jint jint_, jstring jstring_, jobject jobject_, jobjectArray arrayObject, jobject list) {

LOGD("test-jni.cpp testParamsDis");

char returnValue[1024];

int i = jint_;

sprintf(returnValue, "%d", i);

appendString(env, returnValue, jstring_);

jstring returnToString = reinterpret_cast<_jstring *>(env->CallObjectMethod(jobject_, methodID_toString));



appendString(env, returnValue, returnToString);

jsize len = env->GetArrayLength(arrayObject);

jobject tmpObject;

jstring tmpString;

for (i = 0; i < len; i++) {

tmpObject = env->GetObjectArrayElement(arrayObject, i);

tmpString = reinterpret_cast<_jstring *>(env->CallObjectMethod(tmpObject, methodID_toString));

appendString(env, returnValue, tmpString);

}

env->DeleteLocalRef(tmpObject);



jclass clazz_list = env->GetObjectClass(list);

jmethodID list_get = env->GetMethodID(clazz_list, "get", "(I)Ljava/lang/Object;");

jmethodID list_size = env->GetMethodID(clazz_list, "size", "()I");



int size = env->CallIntMethod(list, list_size);

jstring itemStr;

for (i = 0; i < size; i++) {

itemStr = reinterpret_cast(env->CallObjectMethod(list, list_get, i));

appendString(env, returnValue, itemStr);

}

return env->NewStringUTF(returnValue);

}
  1. 在gradle中配置cmake编译选项,并编写CMakeLists.txt脚本,用于生成so


android {

externalNativeBuild {

cmake {

path "CMakeLists.txt"

}

}

}

#CMakeList.txt 内容如下:

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

set(distribution_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)

add_library( # Sets the name of the library.

test-jni

# Sets the library as a shared library.

SHARED

# Provides a relative path to your source file(s).

src/main/cpp/test-jni.cpp)



find_library( # Sets the name of the path variable.

test

# Specifies the name of the NDK library that

# you want CMake to locate.

log)



target_link_libraries( # Specifies the target library.

test-jni

# Links the target library to the log library

# included in the NDK.

${log-lib} )
  1. 在java层,加载本地方法前,加载so


public class JNIMethods {

static {

//加载so

System.loadLibrary("test-jni");

}

//隐式注册

public native String stringFromJNI();

public static native String testParams(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist);



//显式注册

public native String stringFromJNIDis();

public static native String testParamsDis(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist);

}
so加载流程

一般我们是通过 System.loadLibrary("test-jni") 的方式来加载libtest-jni.so库的,一起来跟踪下整个加载过程(此处分析的源码是基于Dalvik的)


System.loadLibrary() -> Runtime.loadLibray0() -> Runtime.doLoad() -> Runtime.nativeLoad()

//java.lang.System

public static void loadLibrary(String libname) {

Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);

}



//java.lang.Runtime

synchronized void loadLibrary0(ClassLoader loader, String libname) {

//......

String error = doLoad(filename, loader);

//......

}



private String doLoad(String name, ClassLoader loader) {

//......

synchronized (this) {

return nativeLoad(name, loader, librarySearchPath);

}

}



// TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.

private static native String nativeLoad(String filename, ClassLoader loader, String librarySearchPath);

在代码中我们并没有看到相关加载nativeLoad这个本地方法的so,那么它又是用什么方式在什么时候被注册的呢?

很多系统的本地方法都是在应用启动的时候通过显示注册的方式被注册的,下面我们来看下系统启动后会做哪些事情。

启动虚拟机进程app_process首先会执行app_main.cpp中的main方法


/frameworks/base/cmds/app_process/app_main.cpp

main() -> AndroidRuntime.start()

int main(int argc, char* const argv[])

{

//......

if (zygote) {

runtime.start("com.android.internal.os.ZygoteInit",

startSystemServer ? "start-system-server" : "");

} else if (className) {

// Remainder of args get passed to startup class main()

runtime.mClassName = className;

runtime.mArgC = argc - i;

runtime.mArgV = argv + i;

runtime.start("com.android.internal.os.RuntimeInit",

application ? "application" : "tool");

}

//......

}

/frameworks/base/core/jni/AndroidRuntime.cpp

start()->startVm()

void AndroidRuntime::start(const char* className, const char* options)

{

//......

/* start the virtual machine */

JniInvocation jni_invocation;

jni_invocation.Init(NULL);

JNIEnv* env;

if (startVm(&mJavaVM, &env) != 0) {//启动vm

return;

}

}



int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)

{



//......

if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {//创建vm

ALOGE("JNI_CreateJavaVM failed\n");

goto bail;

}

//......

}

JNI_CreateJavaVM()在art和dalvik中都有相关实现,这里仅以dalvik为主线跟踪,art的实现在/art/runtime/jni_internal.cc中

/dalvik/vm/Jni.cpp

JNI_CreateJavaVM() -> Init.dvmStartUp()

jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {

//......

JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);

/* Initialize VM. */

gDvm.initializing = true;

std::string status = dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);//初始化vm

//......

}

/dalvik/vm/Init.cpp

dvmStartUp()-> InternalNative.dvmInternalNativeStartup()

dvmStartUp()-> registerSystemNatives() -> jni.RegisterNatives()

std::string dvmStartup(int argc, const char* const argv[],

bool ignoreUnrecognized, JNIEnv* pEnv)

{

//......

if (!dvmNativeStartup()) {//初始化native

return "dvmNativeStartup failed";

}

if (!dvmInternalNativeStartup()) {//初始化内部native

return "dvmInternalNativeStartup failed";

}

if (!dvmJniStartup()) {//初始化jni

return "dvmJniStartup failed";

}

//......

if (!registerSystemNatives(pEnv)) {//注册系统本地方法

return "couldn't register system natives";

}

//......

return "";

}



static bool registerSystemNatives(JNIEnv* pEnv)

{

//......

JNIEXPORT jobject JNICALL Java_java_lang_Class_getDex(JNIEnv* env, jclass javaClass);

const JNINativeMethod Java_java_lang_Class[] = {

{ "getDex", "()Lcom/android/dex/Dex;", (void*) Java_java_lang_Class_getDex },

};

//注册Dex的getDex方法


if (pEnv->RegisterNatives(c, Java_java_lang_Class, 1) != JNI_OK) {

dvmAbort();

}

//加载javacore和nativehelper库

loadJniLibrary("javacore");

loadJniLibrary("nativehelper");

// Back to run mode.

self->status = THREAD_RUNNING;

return true;

}

/dalvik/vm/native/InternalNative.cpp

dvmInternalNativeStartup()

bool dvmInternalNativeStartup()

{

DalvikNativeClass* classPtr = gDvmNativeMethodSet;//遍历系统类结构体,并保存到gDvm的全局变量userDexFiles中

while (classPtr->classDescriptor != NULL) {

classPtr->classDescriptorHash =

dvmComputeUtf8Hash(classPtr->classDescriptor);

classPtr++;

}

gDvm.userDexFiles = dvmHashTableCreate(2, dvmFreeDexOrJar);

if (gDvm.userDexFiles == NULL)

return false;

return true;

}



//系统本地方法都存在这里

static DalvikNativeClass gDvmNativeMethodSet[] = {

{ "Ljava/lang/Object;", dvm_java_lang_Object, 0 },

{ "Ljava/lang/Class;", dvm_java_lang_Class, 0 },

//……

{ "Ljava/lang/Runtime;", dvm_java_lang_Runtime, 0 },

{ "Ljava/lang/String;", dvm_java_lang_String, 0 },

{ "Ljava/lang/System;", dvm_java_lang_System, 0 },

//……

{ "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 },

{ "Ldalvik/system/VMRuntime;", dvm_dalvik_system_VMRuntime, 0 },

//……

};



//调用本地方法会到这里来寻找系统本地方

DalvikNativeFunc dvmLookupInternalNativeMethod(const Method* method)

{

const char* classDescriptor = method->clazz->descriptor;

const DalvikNativeClass* pClass;

u4 hash;

hash = dvmComputeUtf8Hash(classDescriptor);

pClass = gDvmNativeMethodSet;//遍历全局本地方法集合,寻找注册的系统本地方法

while (true) {

if (pClass->classDescriptor == NULL)

break;

if (pClass->classDescriptorHash == hash &&

strcmp(pClass->classDescriptor, classDescriptor) == 0)

{

const DalvikNativeMethod* pMeth = pClass->methodInfo;

while (true) {

if (pMeth->name == NULL)

break;

if (dvmCompareNameDescriptorAndMethod(pMeth->name,

pMeth->signature, method) == 0)

{

/* match */

return pMeth->fnPtr;

}

pMeth++;

}

}

pClass++;

}

return NULL;

}

注册本地方法


/dalvik/vm/Jni.cpp

RegisterNatives()-> dvmRegisterJNIMethod()-> dvmUseJniBridge() -> Class.dvmSetNativeFunc()

static jint RegisterNatives(JNIEnv* env, jclass jclazz, const JNINativeMethod* methods, jint nMethods)

{

//......

for (int i = 0; i < nMethods; i++) {

if (!dvmRegisterJNIMethod(clazz, methods[i].name,

methods[i].signature, methods[i].fnPtr))

{

return JNI_ERR;

}

}

return JNI_OK;

}



static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,

const char* signature, void* fnPtr)

{

//......

if (method->nativeFunc != dvmResolveNativeMethod) {

/* this is allowed, but unusual */

ALOGV("Note: %s.%s:%s was already registered", clazz->descriptor, methodName, signature);

}

method->fastJni = fastJni;

dvmUseJNIBridge(method, fnPtr);

ALOGV("JNI-registered %s.%s:%s", clazz->descriptor, methodName, signature);

return true;

}



void dvmUseJNIBridge(Method* method, void* func) {

//……

DalvikBridgeFunc bridge = gDvmJni.useCheckJni ? dvmCheckCallJNIMethod : dvmCallJNIMethod;

dvmSetNativeFunc(method, bridge, (const u2*) func);//将nativeFunc指向dvmCallJNIMethod,并将insns指向func

}



void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) {

//......

JNIEnv* env = self->jniEnv;

COMPUTE_STACK_SUM(self);

dvmPlatformInvoke(env, (ClassObject*) staticMethodClass,

method->jniArgInfo, method->insSize, modArgs, method->shorty,

(void*) method->insns, pResult);//调用方法insns,对于不同的cpu架构会有不同的调用方式

//......

}

/dalvik/vm/oo/Class.cpp

dvmSetNativeFunc()

void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func, const u2* insns)

{

//......

//将nativeFunc指向dvmCallJNIMethod,并将insns指向func

if (insns != NULL) {

/* update both, ensuring that "insns" is observed first */

method->insns = insns;

android_atomic_release_store((int32_t) func,

(volatile int32_t*)(void*) &method->nativeFunc);

} else {

/* only update nativeFunc */

method->nativeFunc = func;

}

//......

}

至此,我们可以知道调用JNI方法最终会通过 Jni.dvmCallJNIMethod() -> dvmPlatformInvoke() 来根据不同cpu架构实现进行调用。

Class被加载的过程

Android中ClassLoader都是继承自BaseDexClassLoader,加载dex是通过BaseDexClassLoader来加载的,会将dex中的信息都保存到DexPathList中,后面加载类的过程如下:


BaseDexClassLoader.findClass() -> DexPathList.findClass() -> 遍历dexElements -> DexFile.loadClassBinaryName() -> DexFile.defineClass() -> DexFile.defineClassNative()

/dalvik/vm/native/dalvik_system_DexFile.cpp

defineClassNative()-> Class.dvmDefineClass()

const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {

//……

{ "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",

Dalvik_dalvik_system_DexFile_defineClassNative },

{ "getClassNameList", "(I)[Ljava/lang/String;",

Dalvik_dalvik_system_DexFile_getClassNameList },

//……

};



static void Dalvik_dalvik_system_DexFile_defineClassNative(const u4* args,

JValue* pResult)

{

//......

clazz = dvmDefineClass(pDvmDex, descriptor, loader);

Thread* self = dvmThreadSelf();

//......

}

/dalvik/vm/oo/Class.cpp

dvmDefineClass() -> findClassNoInit() -> dvmLookupClass()

-> loadClassFromDex() -> loadClassFromDex0() -> loadMethodFromDex() -> dvmResolveNativeMethod()

ClassObject* dvmDefineClass(DvmDex* pDvmDex, const char* descriptor,

Object* classLoader)

{

assert(pDvmDex != NULL);

return findClassNoInit(descriptor, classLoader, pDvmDex);

}



static ClassObject* findClassNoInit(const char* descriptor, Object* loader,

DvmDex* pDvmDex)

{

Thread* self = dvmThreadSelf();

ClassObject* clazz;

//......

clazz = dvmLookupClass(descriptor, loader, true);//从gDvm全局变量的loadedClasses中找

if (clazz == NULL) {

clazz = loadClassFromDex(pDvmDex, pClassDef, loader);//没找到,则从dex中找

}

//......

return clazz;

}



static ClassObject* loadClassFromDex(DvmDex* pDvmDex, const DexClassDef* pClassDef, Object* classLoader)

{

ClassObject* result;

//......

result = loadClassFromDex0(pDvmDex, pClassDef, &header, pEncodedData,

classLoader);

//......

return result;

}



static ClassObject* loadClassFromDex0(DvmDex* pDvmDex,

const DexClassDef* pClassDef, const DexClassDataHeader* pHeader,

const u1* pEncodedData, Object* classLoader)

{

ClassObject* newClass = NULL;

//......

loadIFieldFromDex(newClass, &field, &newClass->ifields[i]);//遍历加载成员变量

//......

loadMethodFromDex(newClass, &method, &newClass->directMethods[i]);//遍历加载方法

//......

newClass->sourceFile = dexGetSourceFile(pDexFile, pClassDef);

return newClass;

}



static void loadMethodFromDex(ClassObject* clazz, const DexMethod* pDexMethod, Method* meth)

{

//......

if (dvmIsNativeMethod(meth)) {//如果是本地方法,会将nativeFunc指向dvmResolveNativeMethod,当执行本地方法时就会执行dvmResolveNativeMethod方法

meth->nativeFunc = dvmResolveNativeMethod;

meth->jniArgInfo = computeJniArgInfo(&meth->prototype);

}

//......

}

上面跟踪下来我们知道每个dex中的本地方法都会执行到 dvmResolveNativeMethod(),我们来看看该方法做了哪些事情?


/dalvik/vm/Native.cpp

dvmResolveNativeMethod() -> InternalNative.dvmLookupInternalNativeMethod()

dvmResolveNativeMethod() -> lookupSharedLibMethod() -> findMethodInLib()

void dvmResolveNativeMethod(const u4* args, JValue* pResult,

const Method* method, Thread* self)

{

//……

/* start with our internal-native methods */

DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);//寻找系统内部本地方法

if (infunc != NULL) {

//……

DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;

dvmSetNativeFunc((Method*) method, dfunc, NULL);

dfunc(args, pResult, method, self);//执行

return;

}

/* now scan any DLLs we have loaded for JNI signatures */

void* func = lookupSharedLibMethod(method);//在动态加载的so中寻找本地方法

if (func != NULL) {

/* found it, point it at the JNI bridge and then call it */

dvmUseJNIBridge((Method*) method, func);

(*method->nativeFunc)(args, pResult, method, self);//执行nativeFunc

return;

}

//……

dvmThrowUnsatisfiedLinkError("Native method not found", method);//没找到本地方法,则会报UnsatisfiedLinkError

}



static void* lookupSharedLibMethod(const Method* method)

{

if (gDvm.nativeLibs == NULL) {

ALOGE("Unexpected init state: nativeLibs not ready");

dvmAbort();

}

return (void*) dvmHashForeach(gDvm.nativeLibs, findMethodInLib,

(void*) method);//先从gDvm全局变量nativeLibs中寻找,没找到,则通过findMethodInLib在so中寻找,找到则保存到nativeLibs中

}



static int findMethodInLib(void* vlib, void* vmethod)

{

const SharedLib* pLib = (const SharedLib*) vlib;

const Method* meth = (const Method*) vmethod;

//......

/*

* First, we try it without the signature.

*/

preMangleCM =

createJniNameString(meth->clazz->descriptor, meth->name, &len);

if (preMangleCM == NULL)

goto bail;

mangleCM = mangleString(preMangleCM, len);

if (mangleCM == NULL)

goto bail;

//通过createJniNameString和mangleString,得到最终函数的名称路径Java_package_methodName

ALOGV("+++ calling dlsym(%s)", mangleCM);

func = dlsym(pLib->handle, mangleCM);//根据动态链接库操作句柄与符号,返回符号对应的地址

//......

return (int) func;

}

系统内部本地方法通过 dvmLookupInternalNativeMethod()方法来寻找具体实现的

通过System.loadLibrary加载的本地方法,则是通过 lookupSharedLibMethod()方法来寻找的

(方法名称格式:Java_package_methodName)


/dalvik/vm/native/InternalNative.cpp

dvmLookupInternalNativeMethod() -> java_lang_runtime.Dalvik_java_lang_Runtime_nativeLoad()

DalvikNativeFunc dvmLookupInternalNativeMethod(const Method* method)

{

const char* classDescriptor = method->clazz->descriptor;

const DalvikNativeClass* pClass;

u4 hash;

hash = dvmComputeUtf8Hash(classDescriptor);

pClass = gDvmNativeMethodSet;//遍历全局本地方法集合,寻找内部注册的本地方法

while (true) {

if (pClass->classDescriptor == NULL)

break;

if (pClass->classDescriptorHash == hash &&

strcmp(pClass->classDescriptor, classDescriptor) == 0)

{

const DalvikNativeMethod* pMeth = pClass->methodInfo;

while (true) {

if (pMeth->name == NULL)

break;

if (dvmCompareNameDescriptorAndMethod(pMeth->name,

pMeth->signature, method) == 0)

{

/* match */

//ALOGV("+++ match on %s.%s %s at %p",

// className, methodName, methodSignature, pMeth->fnPtr);

return pMeth->fnPtr;

}

pMeth++;

}

}

pClass++;

}

return NULL;

}



static DalvikNativeClass gDvmNativeMethodSet[] = {

{ "Ljava/lang/Object;", dvm_java_lang_Object, 0 },

{ "Ljava/lang/Class;", dvm_java_lang_Class, 0 },

//……

{ "Ljava/lang/Runtime;", dvm_java_lang_Runtime, 0 },

{ "Ljava/lang/String;", dvm_java_lang_String, 0 },

{ "Ljava/lang/System;", dvm_java_lang_System, 0 },

{ "Ljava/lang/Throwable;", dvm_java_lang_Throwable, 0 },

{ "Ljava/lang/VMClassLoader;", dvm_java_lang_VMClassLoader, 0 },

{ "Ljava/lang/VMThread;", dvm_java_lang_VMThread, 0 },

//……

{ "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 },

{ "Ldalvik/system/VMRuntime;", dvm_dalvik_system_VMRuntime, 0 },

//……

};

/dalvik/vm/native/java_lang_runtime.cpp

java_lang_runtime.Dalvik_java_lang_Runtime_nativeLoad() -> Native.dvmLoadNativeCode()

const DalvikNativeMethod dvm_java_lang_Runtime[] = {

//……

{ "nativeExit", "(I)V",

Dalvik_java_lang_Runtime_nativeExit },

{ "nativeLoad", "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/String;",

Dalvik_java_lang_Runtime_nativeLoad },

//……

};



static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,

JValue* pResult)

{

//......

bool success = dvmLoadNativeCode(fileName, classLoader, &reason);

//......

}

/dalvik/vm/Native.cpp

dvmLoadNativeCode() -> JNI_OnLoad()

bool dvmLoadNativeCode(const char* pathName, Object* classLoader,

char** detail)

{

SharedLib* pEntry;

//......

pEntry = findSharedLibEntry(pathName);//so被加载过一次之后,就会缓存起来

if (pEntry != NULL) {

//......

return true;

}

//……

//so第一次被加载

handle = dlopen(pathName, RTLD_LAZY);//打开so动态链接库,保存句柄到handle

//......

/* create a new entry */

SharedLib* pNewEntry;

pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));

pNewEntry->pathName = strdup(pathName);

pNewEntry->handle = handle;//保存handle到pNewEntry

//……

SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);//将pNewEntry保存到gDvm全局变量nativeLibs中,下次可以直接通过缓存获取

//......

vonLoad = dlsym(handle, "JNI_OnLoad");//第一次加载so时,调用so中的JNI_OnLoad方法

//......

return result;

}

}

上面分析总结:

通过System.loadLibrary()加载的库会被保存到全局变量gDvm.nativeLibs中,并执行其JNI_OnLoad方法。

JNI显式注册是直接将本地方法调用地址指向到c层某方法,并将执行方法改成dvmCallJNIMethod(),隐式是通过方法命名规则去寻找方法的调用地址,然后通过dvmCallJNIMethod()来执行类被加载时,所有的本地方法执行地址nativeFunc都被指向了Native.dvmResolveNativeMethod()

通过JNI显式注册会将本地方法调用地址执行C能某实现方法,并将执行地址nativeFunc指向dvmCallJNIMethod(),隐式注册则是在调用的时候才会通过命名规则去寻找方法的调用地址,寻找到之后,再将执行地址nativeFunc指向dvmCallJNIMethod()

所以执行native方法时,nativeFunc未被指向到dvmCallJNIMethod()的方法(包括第一次执行的隐式注册方法和未被注册的系统方法)都会通过

Native.dvmResolveNativeMethod()来处理,针对系统本地方法,会从 InternalNative.gDvmNativeMethodSet 中寻找;针对应用本地方法,则从 gDvm.nativeLibs 中寻找。

当第二次执行同一个方法时,则会直接执行dvmCallJNIMethod(),所以隐式注册相对显示注册来说,第一次调用的时候会慢一点,但是初始化快。

Jni.dvmCallJNIMethod()执行本地方法,是通过调用dvmPlatformInvoke() 来根据不同cpu架构实现方式来执行的。

so加载核心过程:

0939e3a8e1e03c10aa6a04334fc488e81a1633d2

class中本地方法加载过程:

e14769917026cc40e07da13df3f86ad5e769c5d4

本地方法调用过程:

b7a5c2745fd939fa51404b0c7803684674038bac

JNIEnv作用及实现

JNIEnv是JNI指针接口,通常我们通过它来调用操作Java对象,它提供了所有的操作java对象的api。

那么JNIEnv又是何时创建,以及如何来操作Java对象的呢?我们一起来看看它的具体实现~

首先我们来看下线程时如何被创建的:


java/lang/Thread.java

new Thread().start() -> VMThread.create()

public synchronized void start() {

checkNotStarted();

hasBeenStarted = true;

VMThread.create(this, stackSize);// VMThread创建

}

/dalvik/vm/native/java_lang_VMThread.cpp

Dalvik_java_lang_VMThread_create() -> Thread.dvmCreateInterpThread()

const DalvikNativeMethod dvm_java_lang_VMThread[] = {

{ "create", "(Ljava/lang/Thread;J)V",

Dalvik_java_lang_VMThread_create },//线程创建方法

{ "currentThread", "()Ljava/lang/Thread;",

Dalvik_java_lang_VMThread_currentThread },

//……

};



static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult)

{

//……

dvmCreateInterpThread(threadObj, (int) stackSize);//创建线程

RETURN_VOID();

}



/dalvik/vm/Thread.cpp

dvmCreateInterpThread() -> interpThreadStart() -> jni.dvmCreateJNIEnv()

bool dvmCreateInterpThread(Object* threadObj, int reqStackSize)

{

//......

Thread* newThread = allocThread(stackSize);

//......

int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart, newThread);//创建线程

// ......

}



/*

* pthread entry function for threads started from interpreted code.

*/

static void* interpThreadStart(void* arg)

{

//......

self->jniEnv = dvmCreateJNIEnv(self);//创建jniEnv

//......

return NULL;

}

JNIEnv的创建


/dalvik/vm/jni.cpp

dvmCreateJNIEnv()

JNIEnv* dvmCreateJNIEnv(Thread* self) {

JavaVMExt* vm = (JavaVMExt*) gDvmJni.jniVm;

//……

newEnv->funcTable = &gNativeInterface;//接口函数

//......

return (JNIEnv*) newEnv;

}



static const struct JNINativeInterface gNativeInterface = {

DefineClass,

FindClass,

//......

CallVoidMethod,

//...

GetFieldID,

GetObjectField,

//...

CallStaticIntMethod,

//......

};



//方法具体实现(下面是callVoidMethodA()具体实现)

#define CALL_VIRTUAL(_ctype, _jname, _retfail, _retok, _isref) \

static _ctype Call##_jname##Method(JNIEnv* env, jobject jobj, \

jmethodID methodID, ...) \

{ \

ScopedJniThreadState ts(env); \

Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \

const Method* meth; \

va_list args; \

JValue result; \

meth = dvmGetVirtualizedMethod(obj->clazz, (Method*)methodID); \

if (meth == NULL) { \

return _retfail; \

} \

va_start(args, methodID); \

dvmCallMethodV(ts.self(), meth, obj, true, &result, args);//方法调用 \

va_end(args); \

if (_isref && !dvmCheckException(ts.self())) \

result.l = (Object*)addLocalReference(ts.self(), result.l); \

return _retok; \

} \

CALL_VIRTUAL(jobject, Object, NULL, (jobject) result.l, true);

//......

/dalvik/vm/interp/Stack.cpp

JNIEnv.CallVoidMethodA() -> Stack.dvmCallMethodA()

void dvmCallMethodA(Thread* self, const Method* method, Object* obj,

bool fromJni, JValue* pResult, const jvalue* args)

{

//......

if (dvmIsNativeMethod(method)) {

TRACE_METHOD_ENTER(self, method);

(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,

method, self);//执行方法

TRACE_METHOD_EXIT(self, method);

}

//……

}

JNIEnv是在Thread创建的时候创建的,因此它是属于线程的,不能线程共享。最终调用方法时通过Stack.dvmCallMethod..相关方法去调用的。至此整个流程就分析完毕了。

结语

通过上面的分析,我们知道了JNI是如何在java和本地代码之间建立桥梁和通讯的。
在使用JNI的时候,需要注意:

1.显式注册和隐式注册的区别
2.JNI_Onload方法的使用
3.JNIEnv的使用
4.JNI方法的命名,以及extern “C"的作用(告诉编译器,这部分代码请使用C来编译)等等


原文发布时间为:2018-11-2

本文作者:MobSDK

本文来自云栖社区合作伙伴“安卓巴士Android开发者门户”,了解相关信息可以关注“安卓巴士Android开发者门户”。

相关文章
|
6月前
|
存储 Java C++
[NDK/JNI系列02] JNI的设计原理与数据类型
[NDK/JNI系列02] JNI的设计原理与数据类型
46 0
[NDK/JNI系列02] JNI的设计原理与数据类型
|
6月前
|
Java API Android开发
[NDK/JNI系列01] NDK与JNI的基本概念与使用场景
[NDK/JNI系列01] NDK与JNI的基本概念与使用场景
68 0
|
5月前
|
Java Android开发 C++
JNI中如何实现日志功能
JNI中如何实现日志功能
74 0
|
6月前
|
Java C++
jni传递对象
【5月更文挑战第4天】
64 2
|
6月前
|
缓存 Java API
[NDK/JNI系列05] JNI引用API
[NDK/JNI系列05] JNI引用API
53 0
|
存储 缓存 编解码
JNI之常见技巧与陷阱
NDK/JNI连载系列
117 0
|
存储 IDE Java
NDK 系列(6):说一下注册 JNI 函数的方式和时机
NDK 系列(6):说一下注册 JNI 函数的方式和时机
125 0
NDK 系列(6):说一下注册 JNI 函数的方式和时机
JNI中访问JList的代码
JNI中访问JList的代码
62 0
|
Java Unix Linux
JNI学习(0)——关于JNI
JNI学习(0)——关于JNI
139 0
JNI学习(0)——关于JNI
|
Android开发
【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 远程调用 目标进程中 libc.so 动态库中的 mmap 函数 二 | 准备参数 | 远程调用 mmap 函数 )
【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 远程调用 目标进程中 libc.so 动态库中的 mmap 函数 二 | 准备参数 | 远程调用 mmap 函数 )
138 0