JNI提供了一種機制,使得在Java 代碼中可使用 C/C++ 的本地層代碼,這種使用主要是指在 Java 代碼中調用 C/C++ 代碼。這種機制爲咱們開啓了一扇門,一扇將Java 代碼與廣闊的 C/C++ 本地層鏈接起來的門。java
基於 android-ndk-r8d 提供的sample 程序——android-ndk-r8d/samples/hello-jni 的代碼,咱們來看一下,如何在 android 應用開發中使用 JNI。android
首先來看在 Java 代碼中須要作些什麼事情:數組
package com.example.hellojni; import android.app.Activity; import android.widget.TextView; import android.os.Bundle; public class HelloJni extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* Create a TextView and set its content. * the text is retrieved by calling a native * function. */ TextView tv = new TextView(this); tv.setText( stringFromJNI() ); setContentView(tv); } /* A native method that is implemented by the * 'hello-jni' native library, which is packaged * with this application. */ public native String stringFromJNI(); /* This is another native method declaration that is *not* * implemented by 'hello-jni'. This is simply to show that * you can declare as many native methods in your Java code * as you want, their implementation is searched in the * currently loaded native libraries only the first time * you call them. * * Trying to call this function will result in a * java.lang.UnsatisfiedLinkError exception ! */ public native String unimplementedStringFromJNI(); /* this is used to load the 'hello-jni' library on application * startup. The library has already been unpacked into * /data/data/com.example.hellojni/lib/libhello-jni.so at * installation time by the package manager. */ static { System.loadLibrary("hello-jni"); } }
上面那些帶 native 修飾符的方法,就是在 Java 代碼中調用,實現卻在本地層的方法。native 修飾符告訴Java 編譯器,暫時不須要去連接這些方法。app
那Java的機制到底要如何去調用到那個在本地層實現的Java method的呢?能夠看到最後的那個static的block,裏面有調用到System.loadLibrary(),這個動做完成的事情,其實就是把實現了java方法的shared library加載進來。java的機制會在這個shared library中找到那個java方法的實現。ide
但是,JVM是怎麼樣知道java方法和本底層函數的對應關係的呢?或者說,在Java Code中,調用了那個native修飾的傢伙,要如何去找到它的實現呢?C/C++中可沒有java的string那種東西。創建Java方法到native方法的映射,也就是告訴Java層,它所調用的那些native方法在本底層對應於哪一個實際的C/C++方法,有兩種方法。一種是經過給C/C++的函數按照某個特定的規則來命名實現的。好比前面的那個public native String stringFromJNI()方法,Java的機制是會知道它的實現應該是在C/C++中的Java_com_example_hellojni_HelloJni_stringFromJNI() 這個函數的。看看這個函數名,是有多麼的冗長啊,這個函數名中要包含對應的Java 方法的package name、class name及method name的信息嘛。能夠看到這個方法名的構成,Java開頭,而後是下劃線,緊接着是下劃線分割的package name,而後跟着的是下劃線和class name,最後是下劃線和方法名。函數
創建Java方法到native方法的映射還有另外的一種讓人感受更爲舒服的方法,那就是JNI_OnLoad機制。一個shared library,若是他裏面有一個JNI_OnLoad(JavaVM* vm, void*)函數,那麼當這個shared library被load起來的時候,它首先就會被調用一次。而後在這個JNI_OnLoad()函數中,就能夠經過JNI提供的一些函數,來顯式地註冊Java 方法到native方法的映射關係了。咱們修改ndk的sample code,來使用JNI_OnLoad這種機制(同時咱們想要加一個限制,那就是隻能基於"jni.h"提供的方法來實現,儘管在libandroid_android.so中已經存在一些很是好用的設施了,以使得咱們的JNI code能夠用ndk提供的設施輕鬆的build過)。以下是前面那個 stringFromJNI()的實現代碼:ui
#include <string.h> #include <jni.h> #include <stdlib.h> #include <android/log.h> #include "JniDebug.h" #include "JniHelper.h" /* This is a trivial JNI example where we use a native method * to return a new VM String. See the corresponding Java source * file located at: * * apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java */ //extern "C" { // JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv * env, jobject thiz ); //}; // //JNIEXPORT jstring //JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, // jobject thiz ) //{ // const char *str = "Hello from JNI !"; // JniDebug(str); // return env->NewStringUTF(str); //} static jstring HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { JniDebug("from HelloJni_stringFromJNI"); const char *str = "Hello from JNI !"; JniDebug(str); return env->NewStringUTF(str); } static JNINativeMethod gMethods[] = { NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"), }; void register_com_example_hellojni(JNIEnv* env) { jniRegisterNativeMethods(env, "com/example/hellojni/HelloJni", gMethods, NELEM(gMethods)); } int JNI_OnLoad(JavaVM* vm, void*) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { abort(); } register_com_example_hellojni(env); JniDebug("JNI_OnLoad in hello-jni"); return JNI_VERSION_1_6; }
來看這段code,就創建Java方法到native方法的映射部分而言,主要作了兩件事情:this
實際上在此處,無論是上面的第一步,仍是第二步,咱們都沒有直接使用"jni.h"中提供的那些函數或結構。而是參照android系統中libcore/luni/src/main/native/下面的那些實現,定義了一個宏來更加方便的創建Java 方法到native方法的映射表。同時參照android系統中libnativehelper的實現,對"jni.h"中直接提供的那些方法進行了一點點的加強。接下來來看這個部分的Code。首先是頭文件JniHelper.h:code
#include "jni.h" #ifdef __cplusplus extern "C" { #endif /* * Register one or more native methods with a particular class. * "className" looks like "java/lang/String". Aborts on failure. * TODO: fix all callers and change the return type to void. */ int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods); #ifdef __cplusplus } #endif /* * For C++ code, we provide inlines that map to the C functions. g++ always * inlines these, even on non-optimized builds. */ #if defined(__cplusplus) inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods); } #endif #define NATIVE_METHOD(className, functionName, signature) \ { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) } # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
接下來是JniHelper.cpp 文件:orm
#include "JniDebug.h" #include "JniHelper.h" #include <stdlib.h> /** * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.) */ template<typename T> class scoped_local_ref { public: scoped_local_ref(C_JNIEnv* env, T localRef = NULL) : mEnv(env), mLocalRef(localRef) { } ~scoped_local_ref() { reset(); } void reset(T localRef = NULL) { if (mLocalRef != NULL) { (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef); mLocalRef = localRef; } } T get() const { return mLocalRef; } private: C_JNIEnv* mEnv; T mLocalRef; // Disallow copy and assignment. scoped_local_ref(const scoped_local_ref&); void operator=(const scoped_local_ref&); }; static jclass findClass(C_JNIEnv* env, const char* className) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); return (*env)->FindClass(e, className); } extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); JniDebug("Registering %s natives", className); scoped_local_ref<jclass> c(env, findClass(env, className)); if (c.get() == NULL) { JniDebug("Native registration unable to find class '%s', aborting", className); abort(); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { JniDebug("RegisterNatives failed for '%s', aborting", className); abort(); } return 0; }
在Java 方法的native實現的那個文件中,咱們看到有一些打log的函數。這個地方也順便看一下在作NDK開發時打log的方法,首先是頭文件:
void JniDebug(const char format[], ...);
這個頭文件,沒什麼可說的。接着來看這個打log的函數的實現:
#include "stdio.h" static const size_t kBufferSize = 256; #define LOG_TAG "hello-jni" #include <android/log.h> #include "JniDebug.h" void JniDebug(const char format[], ...) { va_list args; va_start(args, format); __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, format, args); va_end(args); }
看上去與咱們日常用的那些printf函數也真心沒有太大的區別。要打log,天然不能忘了要在jni的Android.mk文件中,創建對於liblog的依賴。像下面這樣:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) ANDROID_SOURCE_TOP=$(HOME)/android/Data/android_src/JellyBean/ LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.cpp \ JniHelper.cpp\ JniDebug.cpp LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)
接下來再來看一看咱們上面的那個hello-jni程序運行的結果。它打出來的log:
03-02 08:44:23.362: D/hello-jni(922): Registering com/example/hellojni/HelloJni natives 03-02 08:44:23.362: D/hello-jni(922): JNI_OnLoad in hello-jni 03-02 08:44:23.512: D/hello-jni(922): from HelloJni_stringFromJNI 03-02 08:44:23.512: D/hello-jni(922): Hello from JNI !
結束。