JNI_OnLoad函數不存在的問題

今天分析一個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++


Android OS加載JNI Lib的方法有兩種:

  • 經過JNI_OnLoad。git

  • 若是JNI Lib實現中沒有定義JNI_OnLoad,則dvm調用dvmResolveNativeMethod進行動態解析。github

PS:我們上面第一部分就是dvm調用dvmResolveNativeMethod進行動態解析,因此log打印No JNI_OnLoad found。數組

從網上查到的深刻解析(此解析模塊代碼引用自網絡)

JNI_OnLoad機制分析

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獲取。

dvmResolveNativeMethod延遲解析機制

若是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);(調用執行)

說完蛋疼Load基礎後該準麼辦?

答案其實就是推薦Android OS加載JNI Lib的方法的經過JNI_OnLoad。由於經過它你能夠幹許多自定義的事,譬如實現本身的本地註冊等。 由於在上面的解析中已經看到了JNI_OnLoad->RegisterNatives->…這兩個關鍵方法。具體細節我們如今再說說。

先來看JNI_OnLoad函數

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組件時會呼叫它,所以在該方法中進行善後清理,資源釋放的動做最爲合適。

再來看RegisterNatives函數

在上面第一部分時咱們看見經過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了。

相關文章
相關標籤/搜索