天天看點

一文深入了解 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的邏輯記憶體模型如下:

一文深入了解 JNI實作機制
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加載核心過程:

一文深入了解 JNI實作機制

class中本地方法加載過程:

一文深入了解 JNI實作機制

本地方法調用過程:

一文深入了解 JNI實作機制
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開發者門戶

”,了解相關資訊可以關注“

”。