Android JNI編程—JNI基礎

來源:互聯網
上載者:User

標籤:ace   規範   定義   ica   pre   s函數   工作   自己   功能   

什麼是JNI,怎麼使用

JNI——Java Native Interface,它是Java平台的一個特性(並不是Android系統特有的)。其實主要是定義了一些JNI函數,讓開發人員可以通過調用這些函數實現Java代碼調用C/C++的代碼,C/C++的代碼也可以調用Java的代碼,這樣就可以發揮各個語言的特點了。那麼怎麼使用JNI呢,一般情況下我們首先是將寫好的C/C++代碼編譯成對應平台的動態庫(windows一般是dll檔案,linux一般是so檔案等),這裡我們是針對Android平台,所以只討論so庫。由於JNI編程支援C和C++編程,這裡我們的栗子都是使用C++,對於C的版本可能會有些差異,但是主要的內容還是一致的,大家可以觸類旁通。

舉一個簡單的例子

這裡還是直接從代碼說起,這樣更加形象和直觀,也便於理解。今天使用的Java代碼如下:

public class AndroidJni {    static{        System.loadLibrary("main");    }    public native void dynamicLog();    public native void staticLog();}

這裡我們定義了兩個聲明為native的方法,並聲明了一塊靜態地區,在該靜態地區類載入名為libmain.so的庫,這裡我們說是libmain.so庫,但是載入的時候卻唯寫了“main”,其實大家只要知道這是約定的就可以了。

C++代碼如下:

#include <jni.h>#define LOG_TAG "main.cpp"#include "mylog.h"static void nativeDynamicLog(JNIEnv *evn, jobject obj){    LOGE("hell main");}JNIEXPORT void JNICALL Java_com_github_songnick_jni_AndroidJni_staticLog (JNIEnv *env, jobject obj){    LOGE("static register log ");}JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog},};JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {    JNIEnv *env;    if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {        return -1;    }    LOGE("JNI_OnLoad comming");    jclass clz = env->FindClass("com/github/songnick/jni/AndroidJni");    env->RegisterNatives(clz, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]));    return JNI_VERSION_1_4;}

這裡引用了兩個標頭檔,jni.h和mylog.h,其中jni.h是定義了很多我們使用的JNI函數和結構體,mylog.h是我自己定義的列印Android log的函數(功能和Java的Log類相同)。
這裡暫時不討論怎麼編譯成so庫以及so庫的一些規範。這裡假定將上面的C++程式編譯成了一個叫libmain.so的檔案。在Java層使用System.loadLibarary("main")方法將該so庫載入起來,使得dynamicLog()、staticLog()和對應的Java_com_github_songnick_jni_AndroidJni_staticLog()、nativeDynamicLog()兩個native方法連結起來,當然這部分工作都是由Java虛擬機器完成的,那麼具體是怎麼完成的,接下來將根據上面的代碼進行分析。

靜態註冊native方法

在上面的代碼中看到了JNIEXPORT和JNICALL關鍵字,這兩個關鍵字是兩個宏定義,他主要的作用就是說明該函數為JNI函數,在Java虛擬機器載入的時候會連結對應的native方法,在AndroidJni.java的類中聲明了staticLog()為native方法,他對應的JNI函數就是Java_com_github_songnick_jni_AndroidJni_staticLog(),那麼是怎麼連結的呢,在Java虛擬機器載入so庫時,如果發現含有上面兩個宏定義的函數時就會連結到對應Java層的native方法,那麼怎麼知道對應Java中的哪個類的哪個native方法呢,我們仔細觀察JNI函數名的構成其實是:Java_PkgName_ClassName_NativeMethodName,以Java為首碼,並且用“_”底線將包名、類名以及native方法名串連起來就是對應的JNI函數了。一般情況下我們可以自己手動的去按照這個規則寫,但是如果native方法特別多,那麼還是有一定的工作量,並且在寫的過程中不小心就有可能寫錯,其實Java給我們提供了javah的工具協助產生相應的標頭檔。在產生的標頭檔中就是按照上面說的規則產生了對應的JNI函數,我們在開發的時候直接copy過去就可以了。這裡上面的代碼為例,在AndroidStudio中編譯後,進入項目的目錄app/build/intermediates/classes/debug下,運行如下命令:

javah -d jni com.github.songnick.jni.AndroidJni

這裡-d指定產生.h檔案存放的目錄(如果沒有就會自動建立),com.github.songnick.jni.AndroidJni表示指定目錄下的class檔案。這裡簡單介紹一下產生的JNI函數包含兩個固定的參數變數,分別是JNIEnv和jobject,其中JNIEnv後面會介紹,jobject就是當前與之連結的native方法隸屬的類對象(類似於Java中的this)。這兩個變數都是Java虛擬機器產生並在調用時傳遞進來的。

動態註冊

上面我們介紹了靜態註冊native方法的過程,就是Java層聲明的native方法和JNI函數是一一對應的,那麼有沒有方法讓Java層的native方法和任意的JNI函數連結起來,當然是可以的,這就得使用動態註冊的方法。接下來就看看如何?動態註冊的。

JNI_OnLoad函數

當我們使用System.loadLibarary()方法載入so庫的時候,Java虛擬機器就會找到這個函數並調用該函數,因此可以在該函數中做一些初始化的動作,其實這個函數就是相當於Activity中的onCreate()方法。該函數前面有三個關鍵字,分別是JNIEXPORT、JNICALL和jint,其中JNIEXPORT和JNICALL是兩個宏定義,用於指定該函數是JNI函數。jint是JNI定義的資料類型,因為Java層和C/C++的資料類型或者對象不能直接相互的引用或者使用,JNI層定義了自己的資料類型,用於銜接Java層和JNI層,至於這些資料類型我們在後面介紹。這裡的jint對應Java的int資料類型,該函數返回的int表示當前使用的JNI的版本,其實類似於Android系統的API版本一樣,不同的JNI版本中定義的一些不同的JNI函數。該函數會有兩個參數,其中*jvm為Java虛擬機器執行個體,JavaVM結構體定義了以下函數:

DestroyJavaVMAttachCurrentThreadDetachCurrentThreadGetEnv

這裡我們使用了GetEnv函數擷取JNIEnv變數,上面的JNI_OnLoad函數中有如下代碼:

JNIEnv *env;if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {    return -1;}

這裡調用了GetEnv函數擷取JNIEnv結構體指標,其實JNIEnv結構體是指向一個函數表的,該函數表指向了對應的JNI函數,我們通過調用這些JNI函數實現JNI編程,在後面我們還會對其進行介紹。

擷取Java對象,完成動態註冊

上面介紹了如何擷取JNIEnv結構體指標,得到這個結構體指標後我們就可以調用JNIEnv中的RegisterNatives函數完成動態註冊native方法了。該方法如下:

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)

第一個參數是Java層對應包含native方法的對象(這裡就是AndroidJni對象),通過調用JNIEnv對應的函數擷取class對象(FindClass函數的參數為需要擷取class對象的類描述符):

jclass clz = env->FindClass("com/test/jni/AndroidJni");

第二個參數是JNINativeMethod結構體指標,這裡的JNINativeMethod結構體是描述Java層native方法的,它的定義如下:

typedef struct {    const char* name;//Java層native方法的名字    const char* signature;//Java層native方法的描述符    void*       fnPtr;//對應JNI函數的指標} JNINativeMethod;

第三個參數為註冊native方法的數量。一般會動態註冊多個native方法,首先會定義一個JNINativeMethod數組,然後將該數組指標作為RegisterNative函數的參數傳入,所以這裡定義了如下的JNINativeMethod數組:

JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog}};

最後調用RegisterNative函數完成動態註冊:

env->RegisterNatives(clz, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]));
JNIEnv結構體

上面提到JNIEnv這個結構體,它就老厲害了,指向一個函數表,該函數表指向一系列的JNI函數,我們通過調用這些JNI函數可以實現與Java層的互動,這裡簡單的看看幾個定義的函數:

..........jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)jboolean GetBooleanField(jobject obj, jfieldID fieldID)jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)CallVoidMethod(jobject obj, jmethodID methodID, ...)CallBooleanMethod(jobject obj, jmethodID methodID, ...)..........
這裡簡單的看看上面的四個函數,GetFieldID()函數是擷取Java對象中某個變數的ID,GetBooleanField()函數是根據變數的ID擷取資料類型為Boolean的變數。GetMethodID()函數是擷取Java對象中對應方法的ID,CallVoidMethod()根據methodID調用對應對象中的方法,並且該方法的傳回值為Void類型。通過這些函數我們可以實現調用Java層的代碼。 

Android JNI編程—JNI基礎

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.