Android下如何通過JNI方法向上提供介面總結
1 什麼是JNI
JNI是Java Native Interface的縮寫,即Java本地介面.從Java1.1開始,JNI標準成為Java平臺的一部分,它允許java程式碼和用其它語言編寫的程式碼進行互動.JNI是本地程式設計介面,它使得在Java虛擬機器(VM)內部執行的Java程式碼能夠與用其他程式語言(如C,C++和組合語言)的應用程式和庫進行互動操作.
在Android中提供的JNI的方式,讓Java程式可以呼叫C語言程式。android中很多Java類都具有native介面,這些native介面就是同本地實現,然後註冊到系統中的.
JNI在Android層次結構中的作用如下圖所示:
在Android中,主要的JNI程式碼在以下的路徑中:
Android原始碼根目錄/frameworks/base/core/jni/
這個路徑中的內容將被編譯成庫libandroid_runtime.so,這就是一個普通的動態庫,被放置在目標系統的/system/lib目錄中.
除此之外,Android還包含其他的JNI庫,例如,媒體部分的JNI目錄frameworks/base/media/jni/中,被編譯成庫libmedia_jni.so.
JNI中的各個檔案實際上就是C++的普通檔案,其命名一般和支援的Java類有對應關係。這種關係是習慣上的寫法,而不是強制的。
在Android中實現的JNI庫,需要連線動態庫libnativehelper.so.
2 註冊JNI方法
在Android原始碼根目錄/frameworks/base/services/jni/目錄下有一個onload.cpp檔案,其內容如下:
[cpp] view plain copy
/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "JNIHelp.h" #include "jni.h" #include "utils/Log.h" #include "utils/misc.h" namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); int register_android_server_BatteryService(JNIEnv* env); int register_android_server_InputApplicationHandle(JNIEnv* env); int register_android_server_InputWindowHandle(JNIEnv* env); int register_android_server_InputManager(JNIEnv* env); int register_android_server_LightsService(JNIEnv* env); int register_android_server_PowerManagerService(JNIEnv* env); int register_android_server_UsbDeviceManager(JNIEnv* env); int register_android_server_UsbHostManager(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); int register_android_server_location_GpsLocationProvider(JNIEnv* env); int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_HelloService(JNIEnv *env); }; using namespace android; extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("GetEnv failed!"); return result; } LOG_ASSERT(env, "Could not retrieve the env!"); register_android_server_PowerManagerService(env); register_android_server_InputApplicationHandle(env); register_android_server_InputWindowHandle(env); register_android_server_InputManager(env); register_android_server_LightsService(env); register_android_server_AlarmManagerService(env); register_android_server_BatteryService(env); register_android_server_UsbDeviceManager(env); register_android_server_UsbHostManager(env); register_android_server_VibratorService(env); register_android_server_SystemServer(env); register_android_server_location_GpsLocationProvider(env); register_android_server_connectivity_Vpn(env); register_android_server_HelloService(env); return JNI_VERSION_1_4; }
onload.cpp檔案上部分為註冊函式的宣告,下部分為呼叫各種註冊函式,而這些註冊函式就是JNI方法的註冊函式! 正有通過這些註冊函式,上層才有可能呼叫註冊的JNI方法.
這些註冊函式是由同目錄下的其他.cpp檔案中實現,如上面的register_android_server_HelloService(env)這個函式是在com_android_service_HelloService.cpp檔案中實現的.那麼編譯器又是如何知道這點的呢? 答案當然是Android.mk這個檔案,開啟這個檔案,其內容如下:
[html] view plain copy
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
com_android_server_AlarmManagerService.cpp \
com_android_server_BatteryService.cpp \
com_android_server_InputApplicationHandle.cpp \
com_android_server_InputManager.cpp \
com_android_server_InputWindowHandle.cpp \
com_android_server_LightsService.cpp \
com_android_server_PowerManagerService.cpp \
com_android_server_SystemServer.cpp \
com_android_server_UsbDeviceManager.cpp \
com_android_server_UsbHostManager.cpp \
com_android_server_VibratorService.cpp \
com_android_server_location_GpsLocationProvider.cpp \
com_android_server_connectivity_Vpn.cpp \
com_android_server_HelloService.cpp \
onload.cpp
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
frameworks/base/services \
frameworks/base/core/jni \
external/skia/include/core
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libcutils \
libhardware \
libhardware_legacy \
libnativehelper \
libsystem_server \
libutils \
libui \
libinput \
libskia \
libgui \
libusbhost
ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
endif
LOCAL_MODULE:= libandroid_servers
include $(BUILD_SHARED_LIBRARY)
在LOCAL_SRC_FILE中給出了所有實現檔案(cpp檔案)的路徑,因此編譯就能找到各個註冊函式對應的實現檔案了.
接下來讓我們來看看其中一個註冊函式的具體實現過程是如何的,比如:register_android_server_HelloService(env),開啟com_android_service_HelloService.cpp檔案,其下有註冊函式的實現程式碼,如下:
[cpp] view plain copy
int register_android_server_HelloService(JNIEnv *env) {
return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));
}
其中jniRegisterNativeMethods為註冊JNI方法函式,此函式在JNI方法使用中非常重要,此函式的第二個引數為對應著java類即HelloService.java的檔名,第三個引數為註冊的方法表:
[cpp] view plain copy
/*JNI方法表*/
static const JNINativeMethod method_table[] = {
{"init_native", "()Z", (void*)hello_init},
{"setVal_native", "(I)V", (void*)hello_setVal},
{"getVal_native", "()I", (void*)hello_getVal},
};
接下來就是方法表內各個介面的實現程式碼了.
如hello_setVal函式的實現:
[cpp] view plain copy
/*通過硬體抽象層定義的硬體訪問介面設定硬體暫存器val的值*/
static void hello_setVal(JNIEnv* env, jobject clazz, jint value) {
int val = value;
LOGI("Hello JNI: set value %d to device.", val);
if(!hello_device) {
LOGI("Hello JNI: device is not open.");
return;
}
hello_device->set_val(hello_device, val);
}
方法列表中的hello_init的實現程式碼中展現瞭如何呼叫下層HAL提供的介面, 還記得上一章: Android中HAL如何向上層提供介面總結 一文中描述HAL是如何向上層提供介面的嗎?這個hello_init函式的實現就是典型的呼叫HAL提供的初始化介面的例子,下面見hello_init這個函式的實現程式碼:
[cpp] view plain copy
/*通過硬體抽象層定義的硬體模組開啟介面開啟硬體裝置*/
static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {
return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
/*通過硬體模組ID來載入指定的硬體抽象層模組並開啟硬體*/
static jboolean hello_init(JNIEnv* env, jclass clazz) {
hello_module_t* module;
LOGI("Hello JNI: initializing......");
if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {
LOGI("Hello JNI: hello Stub found.");
if(hello_device_open(&(module->common), &hello_device) == 0) {
LOGI("Hello JNI: hello device is open.");
return 0;
}
LOGE("Hello JNI: failed to open hello device.");
return -1;
}
LOGE("Hello JNI: failed to get hello stub module.");
return -1;
}
上述的module->methods->open這個open函式就是HAL提供的介面,其函式原型在hardware.h標頭檔案中有定義,只能返回struct hw_device_t型別的指標,而在JNI方法中,我們關心的是struct hello_device_t,只有通過struct hello_device_t,我們才能獲取其所有的成員函式(接HAL提供的介面函式),由於struct hello_device_t的第一個成員就是struct hw_device_t型別的資料,因此在這裡可以將獲取的struct hw_device_t強制轉化為struct hello_device_t來用。還沒有明白過來的,建議回過頭去看上一篇文章:Android中HAL如何向上層提供介面總結 .
3 方法列表說明
關於static const JNINativeMethod method_table[]方法列表的原型如下:
[cpp] view plain copy
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
Andoird 中使用了一種不同傳統Java JNI的方式來定義其native的函式。其中很重要的區別是Andorid使用了一種Java 和 C 函式的對映表陣列,並在其中描述了函式的引數和返回值。這個陣列的型別就是JNINativeMethod,見上述定義.
第一個變數name是Java中函式的名字。第二個變數signature,用字串是描述了函式的引數和返回值.第三個變數fnPtr是函式指標,指向C函式。
其中比較難以理解的是第二個引數,例如:
“()V”
“(II)V”
“(Ljava/lang/String;Ljava/lang/String;)V”
實際上這些字元是與函式的引數型別一一對應的。
“()” 中的字元表示引數,後面的則代表返回值。例如”()V” 就表示void Func();
“(II)V” 表示 void Func(int, int);
具體的每一個字元的對應關係如下
字元 Java型別 C型別
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
陣列則以”[“開始,用兩個字元表示
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
上面的都是基本型別。如果Java函式的引數是class,則以”L”開頭,以”;”結尾.中間是用”/” 隔開的包及類名。而其對應的C函式名的引數則為jobject. 一個例外是String類,其對應的類為jstring
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
如果JAVA函式位於一個嵌入類,則用$作為類名間的分隔符。
例如 “(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z”