JNI函數(shù)靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)

系統(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è)流程

  1. 從java代碼System.loadLibrary(libName);開始(libcore\ojluni\src\main\java\java\lang)此熬;
  2. 根據(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
        }
    }
  1. 上面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));
}
  1. 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());
}
  1. 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è)的不足之處。

參考博客
so庫加載System.loadLibrary流程分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末攀甚,一起剝皮案震驚了整個(gè)濱河市箩朴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秋度,老刑警劉巖炸庞,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異荚斯,居然都是意外死亡埠居,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門事期,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滥壕,“玉大人,你說我怎么就攤上這事兽泣∫镩伲” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵唠倦,是天一觀的道長称鳞。 經(jīng)常有香客問我,道長稠鼻,這世上最難降的妖魔是什么胡岔? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮枷餐,結(jié)果婚禮上靶瘸,老公的妹妹穿的比我還像新娘苫亦。我一直安慰自己,他們只是感情好怨咪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布屋剑。 她就那樣靜靜地躺著,像睡著了一般诗眨。 火紅的嫁衣襯著肌膚如雪唉匾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天匠楚,我揣著相機(jī)與錄音巍膘,去河邊找鬼。 笑死芋簿,一個(gè)胖子當(dāng)著我的面吹牛峡懈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播与斤,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肪康,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了撩穿?” 一聲冷哼從身側(cè)響起磷支,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎食寡,沒想到半個(gè)月后雾狈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抵皱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年箍邮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叨叙。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锭弊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出擂错,到底是詐尸還是另有隱情味滞,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布钮呀,位于F島的核電站剑鞍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏爽醋。R本人自食惡果不足惜蚁署,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚂四。 院中可真熱鬧光戈,春花似錦哪痰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至筷弦,卻和暖如春肋演,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烂琴。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工爹殊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奸绷。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓梗夸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親健盒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容