JNI兩種註冊過程

JNI兩種註冊過程實戰

原文鏈接:https://blog.csdn.net/XSF50717/article/details/54693802

JNI兩種註冊過程實戰

深入理解JNI

概述
Android OS加載JNI Lib的方法有兩種
- JNI_OnLoad(動態註冊)
- 如果JNI Lib實現中沒有定義JNI_OnLoad,則dvm調用dvm ResolveNativeMethod進行動態解析(靜態註冊)
因此,當 java 通過 System.loadLibrary 加載完 JNI 動態庫後,緊接着會調用 JNI_OnLoad 的函數。
本文主要介紹JNI實戰中的兩種方式(靜態註冊和動態註冊)並比較二者的優劣和實戰中的一些坑

0x00 靜態註冊
靜態註冊基本原理
根據函數名來建立java方法和JNI函數間的一一對應關係。

靜態有兩個非常重要的關鍵字JNIEXPORT和JNICALL,這兩個關鍵字時宏定義,主要用於說明該函數是JNI函數,在虛擬機加載so庫時,如果發現函數含有上面兩個宏定義時,就會鏈接到對應java層的native方法。

如何使用靜態註冊
主要是幾個關鍵點

先寫java代碼,編譯生成class文件
使用 javah -jni 包名.文件名 (按照網上使用的方式不好使,在java目錄下成功得到.h文件)
配置文件
1 gradle.progerties文件下添加如下代碼

android.useDeprecatedNdk=true
1
這裏要說明一下,構建jni模塊工程有3種方式

最古老的方式 (非必要)【不使用gralde+手動生成文件+手動ndk-build】
手動寫 Android.mk、Applicatoin.mk ,然後手動調用ndk-build去生成so包,早期在ADT時代開發常用

最前沿的方式 使用gradle-experimental 【使用gradle+全自動生成】
這表示使用當前版本 Gradle 插件,繼續使用過時的NDK。由於google爲NDK開發提供了一個實驗性的工具gradle-experimental但截止目前爲止最新版本爲0.7.3,從版本號上來看目前仍在實驗性,而且需要替換project和app的graldebuilde.gradle,需要改寫app的gradle,使用model的方式,改動還是比較大的,但是帶來了很大方便,可以直接創建對應的h文件,參考文獻[2-4]介紹了這種最新的方式

折中的方式【使用gradle+手動生成頭文件+自動ndk-build】

這種方便對當前gradle無改變,唯一需要的是手動生成JNI的頭文件(也可以通過配置external tool來自動生成[5])就可以。本文就是採取了這種方式來構建自己的Jni,而自動構建ndk-build就是交給了android.useDeprecatedNdk=true,表示使用ndk來build(因爲谷歌出了gradle-experimental更推薦這種方式,但是這種方式改動較大、也不穩定)。

2 build.gradle文件在defaultConfig文件中

        //這裏是配置ndk
         ndk{
            moduleName "xsfJni"
            ldLibs "log" //添加依賴庫,因爲有log等打印
            abiFilters "armeabi", "armeabi-v7a", "x86"  //輸出指定三種abi體系結構下的so庫。
        }

3 寫.c文件(注意與.h文件相同),並且在natvie方法中加載工具類

    //加載靜態庫
    static {
        System.loadLibrary("Test");//此處加載的是相應的模塊庫,名稱必須和 ndk的moduleName名一樣。
    }

在這裏使用了以前的,可以看下定義的Util類和使用javah -jni生成的h頭文件

public class NDKUtils {
    static {
        System.loadLibrary("xsfJni");   //defaultConfig.ndk.moduleName
    }
    //測試靜態方法Jni
    public native String  getVipString();
    public native String  generateKey(String name);
}

通過在 java文件夾下執行 javah -jni xsf.jnidemo.NDKUtils 得到h文件(當然你也可以自己手搓一個,只是容易寫錯而已),截取其中部分生成代碼

#include <jni.h>

#ifndef _Included_xsf_jnidemo_NDKUtils
#define _Included_xsf_jnidemo_NDKUtils
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL Java_xsf_jnidemo_NDKUtils_getVipString
  (JNIEnv *, jobject);
JNIEXPORT jstring JNICALL Java_xsf_jnidemo_NDKUtils_generateKey
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

可以看出JNI調用函數名稱是按照一定的規則去生成的,規則如下:

java_完整包名_類名_方法名
1
靜態註冊弊端:

後期類名、文件名改動,頭文件所有函數將失效,需要手動改,超級麻煩易出錯

代碼編寫不方便,由於JNI層函數的名字必須遵循特定的格式,且名字特別長;

會導致程序員的工作量很大,因爲必須爲所有聲明瞭native函數的java類編寫JNI頭文件;

程序運行效率低,因爲初次調用native函數時需要根據根據函數名在JNI層中搜索對應的本地函數,然後建立對應關係,這個過程比較耗時。

0x01動態註冊
靜態註冊JNI弊端多多,因此使用動態註冊JNI十分有必要。

動態註冊的原理
直接告訴native函數其在JNI中對應函數的指針;

動態註冊的原理是這樣的:JNI 允許我們提供一個函數映射表,註冊給 JVM,這樣 JVM 就可以用函數映射表來調用相應的函數,
而不必通過函數名來查找相關函數(這個查找效率很低,函數名超級長)。

實現過程:

利用結構體JNINativeMethod保存Java Native函數和JNI函數的對應關係;

在一個JNINativeMethod數組中保存所有native函數和JNI函數的對應關係;

在Java中通過System.loadLibrary加載完JNI動態庫之後,調用JNI_OnLoad函數,開始動態註冊;

JNI_OnLoad中會調用AndroidRuntime::registerNativeMethods函數進行函數註冊;

AndroidRuntime::registerNativeMethods中最終調用jniRegisterNativeMethods完成註冊。

如何使用動態註冊
在NDKUtils中添加一個動態使用JNI的方法

//測試動態方法Jni
public native String  dynamicGenerateKey(String name);

函數映射表
JNINativeMethod這其實是一個結構體,在jni.h頭文件中定義,通過這個結構體從而使Java與jni建立聯繫

typedef struct {

const char* name; //Java中函數的名字

const char* signature;//符號簽名,描述了函數的參數和返回值

void* fnPtr;//函數指針,指向一個被調用的函數

} JNINativeMethod;

關於簽名符號可以參考我之前的總結http://blog.csdn.net/xsf50717/article/details/51598748

回到正題,在我們創建的C文件中,上面的dynamicGenerateKey函數映射表如下

//函數映射表
static JNINativeMethod methods[] = {
        {"dynamicGenerateKey", "(Ljava/lang/String;)Ljava/lang/String;", (void *) native_dynamic_key},
        //這裏可以有很多其他映射函數
};

JNI_OnLoad()函數
在開篇我們就提過當 java 通過 System.loadLibrary 加載完 JNI 動態庫後,緊接着會調用 JNI_OnLoad 的函數。這個函數主要有兩個作用

指定Jni版本
告訴JVM該組件使用哪一個jni版本(若未提供JNI_Onload函數,JVM會默認使用最老的JNI1.1版本),如果要使用新的版本的JNI,如JNI 1.4版本,則必須由JNI_OnLoad()函數返回常量JNI_VERSION_1_4(該常量定義在jni.h中)來告知JVM
- 初始化

當JVM執行到System.loadLibrary() 函數時,會立即調用 JNI_OnLoad() 方法,因此在該方法中進行各種資源的初始化操作最爲恰當,

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    LOGD("-------------JNI_OnLoad into.--------\n");
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK)
    {
        return -1;
    }
    assert(env != NULL);
    //動態註冊,自定義函數
    if (!registerNatives(env))
    {
        return -1;
    }

    return JNI_VERSION_1_4;
}

動態註冊
RegisterNatives,動態註冊就是在這裏完成的,函數原型在jni.h中

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)
1
該函數有3個參數

clazz
java類名,通過FindClass得到

methods
JNINativeMethod的結構體指針

mMethods
方法個數

在該例子中代碼如下

//註冊Native
static int registerNatives(JNIEnv *env) {
    const char *className = "xsf/jnidemo/NDKUtils"; //指定註冊的類
    return registerNativeMethods(env, className, methods, sizeof(methods) / sizeof(methods[0]));
}

static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
                                 int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }

    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

這樣動態註冊就完成了

0x01 效果圖&code


學習code上傳到github https://github.com/xsfelvis/JniDemo

0x02 參考
[1] http://blog.csdn.net/yanbober/article/details/51027520
[2] http://tools.android.com/tech-docs/new-build-system/gradle-experimental
[3] http://blog.csdn.net/sbsujjbcy/article/details/48469569
[4] http://www.jianshu.com/p/7844aafe897d
[5] http://blog.majiajie.me/2016/03/27/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E5%9C%B0%E4%BD%BF%E7%94%A8NDK/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章