系統(tǒng)加載lib的方法:
- 通過JNI_OnLoad,實(shí)現(xiàn)動(dòng)態(tài)注冊(cè)筷转;
- 如果沒有定義JNI_OnLoad矫限,則dvm調(diào)用dvmResolveNativeMethod進(jìn)行動(dòng)態(tài)解析。其實(shí)就是保存JNI層函數(shù)的函數(shù)指針感混,調(diào)用方法時(shí)直接使用這個(gè)函數(shù)指針。
靜態(tài)注冊(cè)是Java的Native方法通過方法指針來與JNI進(jìn)行關(guān)聯(lián)礼烈,動(dòng)態(tài)注冊(cè)可以讓Native方法知道它在JNI中對(duì)應(yīng)的方法指針弧满。
JNI中有一種結(jié)構(gòu)用來記錄Java的Native方法和JNI方法的關(guān)聯(lián)關(guān)系,它就是JNINativeMethod
typedef struct {
const char* name; // Java方法的名字
const char* signature; //Java方法的簽名信息
void* fnPtr; //JNI中對(duì)應(yīng)的方法指針
} JNINativeMethod;
注冊(cè)流程
- 從java代碼
System.loadLibrary(libName);
開始(libcore\ojluni\src\main\java\java\lang
)此熬; - 根據(jù)
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
來到java/lang/Runtime.java
里庭呜,代碼如下
// libcore\ojluni\src\main\java\java\lang\Runtime.java 注意這個(gè)Runtime是java核心庫里面的和AndroidRuntime無關(guān)系
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
} // 檢測名稱合法性
String libraryName = libname;
...
String error = doLoad(filename, loader);//如果庫文件存在,就加載
if (error != null) {
throw new UnsatisfiedLinkError(error);
}//加載庫文件失敗犀忱,拋出異常
return;
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : getLibPaths()) { / / getLibPaths()用來獲取系統(tǒng)中存放so庫的文件路徑
String error = doLoad(candidate, loader); // 加載該庫
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
private String doLoad(String name, ClassLoader loader) {
if (loader != null && loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
librarySearchPath = dexClassLoader.getLdLibraryPath();
}
synchronized (this) {
return nativeLoad(name, loader, librarySearchPath);// 調(diào)用本地方法nativeLoad
}
}
- 上面doLoad()->nativeLoad(); 本地方法對(duì)應(yīng)的源文件為java_lang_Runtime.cc募谎;
// libcore\ojluni\\src\main\native\Runtime.c 注意這個(gè)Runtime是java核心庫里面的和AndroidRuntime無關(guān)系
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath)
{
// //調(diào)用JVM_NativeLoad方法,該方法申明在jvm.h中阴汇,實(shí)現(xiàn)在OpenjdkJvm.cc中
return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Runtime, freeMemory, "!()J"),
NATIVE_METHOD(Runtime, totalMemory, "!()J"),
NATIVE_METHOD(Runtime, maxMemory, "!()J"),
NATIVE_METHOD(Runtime, gc, "()V"),
NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
NATIVE_METHOD(Runtime, nativeLoad,
"(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)"
"Ljava/lang/String;"),
};
void register_java_lang_Runtime(JNIEnv* env) {
jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}
- art/runtime/openjdkjvm/OpenjdkJvm.cc
// art/runtime/openjdkjvm/OpenjdkJvm.cc
#include "../../libcore/ojluni/src/main/native/jvm.h"
//JVM_NativeLoad方法的實(shí)現(xiàn)
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jstring javaLibrarySearchPath) {
...
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
javaLibrarySearchPath,
&error_msg); // 調(diào)用JavaVMExt的LoadNativeLibrary方法
if (success) {
return nullptr;
}
...
return env->NewStringUTF(error_msg.c_str());
}
- LoadNativeLibrary
//art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jstring library_path,
std::string* error_msg) {
...
sym = library->FindSymbol("JNI_OnLoad", nullptr);
// 在我們要加載so庫中查找JNI_OnLoad方法数冬,如果沒有就認(rèn)為是靜態(tài)注冊(cè)方式,代表so庫加載成功搀庶,如果找到JNI_OnLoad就會(huì)調(diào)用JNI_OnLoad方法拐纱,
// JNI_OnLoad方法中一般存放的是方法注冊(cè)的函數(shù),所以如果采用動(dòng)態(tài)注冊(cè)就必須要實(shí)現(xiàn)JNI_OnLoad方法
if (sym == nullptrr) {
was_successful = true;
} else {
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);//定義了一個(gè)
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
// sym是void*類型的哥倔,任何一個(gè)類型都可以用void*類型進(jìn)行傳遞秸架,但是void*類型是不能夠調(diào)用的,
// 所用在調(diào)用之前咆蒿,需要將void*代表的類型轉(zhuǎn)換為其原來的類型东抹,在這里蚂子,把sym重新解釋為JNI_OnLoadFn,
// sym指向的是JNI_OnLoad缭黔,JNI_OnLoad和JNI_OnLoadFn是相同的類型食茎,其實(shí)在我的理解中就是讓JNI_OnLoadFn指向JNI_OnLoad函數(shù)的地址,
// 這樣調(diào)用JNI_OnLoadFn就像調(diào)用JNI_OnLoad一樣
int version = (*jni_on_load)(this, nullptr);//調(diào)用JNI_OnLoad函數(shù)馏谨,version為JNI_OnLoad函數(shù)的返回值
}
...
動(dòng)態(tài)注冊(cè)和靜態(tài)注冊(cè)代碼示例
#include <jni.h>
#include <string>
#include<android/log.h>
#define TAG "test-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定義LOGI類型
/**
* 靜態(tài)注冊(cè)方式董瞻,方法名按照標(biāo)準(zhǔn)寫法Java_包名_類名_方法名
*/
extern "C" JNIEXPORT jstring JNICALL Java_com_jianjin33_demo2_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */)
{
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
// 接下來是動(dòng)態(tài)注冊(cè)的方式,需要實(shí)現(xiàn)jni.h中的JNI_OnLoad方法
// 方法名可以隨意些
extern "C" JNIEXPORT jstring JNICALL native_stringFromJni2(JNIEnv *env, jclass clazz)
{
return env->NewStringUTF("動(dòng)態(tài)注冊(cè)jni函數(shù)返回結(jié)果");
}
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
#define JNIREG_CLASS "com/jianjin33/demo2/MainActivity" // 指定要注冊(cè)的類
// JNINativeMethod結(jié)構(gòu)體就是文章開頭介紹的
static JNINativeMethod gMethods[] = {
{ "stringFromJni2", "()Ljava/lang/String;", (void*)native_stringFromJni2}, // 綁定
};
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
LOGI("%s","開始走JNI_OnLoad");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) { // 注冊(cè)
return -1;
}
result = JNI_VERSION_1_6;
return result;
}
java代碼調(diào)用jni
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI(); // 靜態(tài)注冊(cè)方式
public native String stringFromJNI2(); // 動(dòng)態(tài)注冊(cè)方式
兩種方式的對(duì)比
靜態(tài)注冊(cè):根據(jù)函數(shù)名來建立Java函數(shù)和JNI函數(shù)之間的關(guān)聯(lián)關(guān)系田巴,要求JNI層函數(shù)的名字必須遵守特定的格式,所以書寫起來比較長挟秤,并且壹哺,初次調(diào)用本地函數(shù)時(shí)要根據(jù)函數(shù)名搜索JNI層函數(shù)來建立關(guān)聯(lián)關(guān)系,會(huì)影響運(yùn)行效率艘刚。
動(dòng)態(tài)注冊(cè):擴(kuò)展性較好管宵,避免了靜態(tài)注冊(cè)的不足之處。