今天分析一個app的老版本時,無心發現JNI_OnLoad不存在,可是so的確是java層load加載,各native函數也有聲明和調用,覺得又遇到什麼黑科技。查找發現最先期的ndk開發版本中的確是沒有這個函數的。java
現期各版本的ndk中JNI_OnLoad函數都是load時自動調用的,若是未發現則去調用dvmResolveNativeMethod。如下時一份詳細的流程解釋。android
如下摘自 http://yanbober.github.io/2015/02/25/android_studio_jni_3/c++
經過JNI_OnLoad。git
若是JNI Lib實現中沒有定義JNI_OnLoad,則dvm調用dvmResolveNativeMethod進行動態解析。github
PS:我們上面第一部分就是dvm調用dvmResolveNativeMethod進行動態解析,因此log打印No JNI_OnLoad found。數組
System.loadLibrary調用流程以下所示:網絡
System.loadLibrary->Runtime.loadLibrary->(Java)nativeLoad->(C: java_lang_Runtime.cpp)Dalvik_java_lang_Runtime_nativeLoad->dvmLoadNativeCode->(dalvik/vm/Native.cpp)app
接着以下:函數
dlopen(pathName, RTLD_LAZY) (把.so mmap到進程空間,並把func等相關信息填充到soinfo中)this
dlsym(handle, 「JNI_OnLoad」)
JNI_OnLoad->RegisterNatives->dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName, const char* signature, void* fnPtr)->dvmUseJNIBridge(method, fnPtr)->(method->nativeFunc = func)
JNI函數在進程空間中的起始地址被保存在ClassObject->directMethods中。
struct ClassObject : Object { /* static, private, and <init> methods */ int directMethodCount; Method* directMethods; /* virtual methods defined in this class; invoked through vtable */ int virtualMethodCount; Method* virtualMethods; }
此ClassObject經過gDvm.jniGlobalRefTable或gDvm.jniWeakGlobalRefLock獲取。
若是JNI Lib中沒有JNI_OnLoad,即在執行System.loadLibrary時,沒法把此JNI Lib實現的函數在進程中的地址增長到ClassObject->directMethods。則直到須要調用的時候纔會解析這些javah風格的函數 。這樣的函數dvmResolveNativeMethod(dalvik/vm/Native.cpp)來進行解析,其執行流程以下所示:
void dvmResolveNativeMethod(const u4* args, JValue* pResult, const Method* method, Thread* self)->(Resolve a native method and invoke it.)
接着以下:
void* func = lookupSharedLibMethod(method)(根據signature在全部已經打開的.so中尋找此函數實現)dvmHashForeach(gDvm.nativeLibs, findMethodInLib,(void*) method)->findMethodInLib(void* vlib, void* vmethod)->dlsym(pLib->handle, mangleCM)
dvmUseJNIBridge((Method*) method, func)
(*method->nativeFunc)(args, pResult, method, self);(調用執行)
答案其實就是推薦Android OS加載JNI Lib的方法的經過JNI_OnLoad。由於經過它你能夠幹許多自定義的事,譬如實現本身的本地註冊等。 由於在上面的解析中已經看到了JNI_OnLoad->RegisterNatives->…這兩個關鍵方法。具體細節我們如今再說說。
JNI_OnLoad()函數主要的用途有兩點:
通知VM此C組件使用的JNI版本。若是你的.so文件沒有提供JNI_OnLoad()函數,VM會默認該.so使用最老的JNI 1.1版本。 而新版的JNI作了許多擴充,若是須要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer, 就必須藉由JNI_OnLoad()函數來告知VM。
由於VM執行到System.loadLibrary()函數時,會當即先調運JNI_OnLoad(),因此C組件的開發者能夠由JNI_OnLoad()來進行C組件內的初期值之設定(Initialization)。
既然有JNI_OnLoad(),那就有相呼應的函數,那就是JNI_OnUnload(),當VM釋放JNI組件時會呼叫它,所以在該方法中進行善後清理,資源釋放的動做最爲合適。
在上面第一部分時咱們看見經過javah命令生成的io_github_yanbober_ndkapplication_NdkJniUtils.h裏函數的名字好長,看着就蛋疼。你確定也想過怎麼這麼長, 並且當有時候項目需求緣由致使類名變了的時候,函數名必須一個一個的改,更加蛋疼。我第一次接觸時那時候本身經驗不足,就趕上了這個蛋疼問題。淚奔啊!
既然這樣那就有解決辦法的,那就是RegisterNatives大招。接下來來看下這個大招:
App的Java程序尋找c本地方法的過程通常是依賴VM去尋找*.so裏的本地函數,若是須要連續調運不少次,每次都要尋找一遍, 會多花許多時間。所以爲了解決這個問題咱們能夠自行將本地函數向VM進行登記,而後讓VM自行調registerNativeMethods()函數。
VM自行調registerNativeMethods()函數的做用主要有兩點:
更加有效率去找到C語言的函數
能夠在執行期間進行抽換,由於自定義的JNINativeMethod類型的methods[]數組是一個名稱-函數指針對照表,在程序執行時, 能夠屢次調運registerNativeMethods()函數來更換本地函數指針,從而達到彈性抽換本地函數的效果。
上面提到的JNINativeMethod結構是c/c++方法和Java方法之間映射關係的關鍵結構,該結構定義在jni.h中,具體定義以下:
typedef struct { const char* name;//java方法名稱 const char* signature; //java方法簽名 void* fnPtr;//c/c++的函數指針 } JNINativeMethod;
所謂自定義的JNINativeMethod類型的methods[]數組天然也就相似長下面這樣了:
static JNINativeMethod methods[] = { {"generateKey", "(Ljava/lang/String;)Ljava/lang/String;", (void*)generateKey}, };
以上也就是所謂的動態註冊JNI了。