參考文獻(xiàn):
http://blog.csdn.net/luoshengyang/article/details/6575988
http://www.androidmi.com/Androidkaifa/rumen/201005/633.html
1 什么是JNI
JNI是Java Native Interface的縮寫,即Java本地接口.從Java1.1開始,JNI標(biāo)準(zhǔn)成為Java平臺的一部分,它允許java代碼和用其它語言編寫的代碼進(jìn)行交互.JNI是本地編程接口,它使得在Java虛擬機(jī)(VM)內(nèi)部運(yùn)行的Java代碼能夠與用其他編程語言(如C,C++和匯編語言)的應(yīng)用程序和庫進(jìn)行交互操作.
在Android中提供的JNI的方式,讓Java程序可以調(diào)用C語言程序。Android中很多Java類都具有native接口,這些native接口就是同本地實(shí)現(xiàn),然后注冊到系統(tǒng)中的.
JNI在Android層次結(jié)構(gòu)中的作用如下圖所示:
在Android中,主要的JNI代碼在以下的路徑中:
Android源碼根目錄/frameworks/base/core/jni/
這個(gè)路徑中的內(nèi)容將被編譯成庫libandroid_runtime.so,這就是一個(gè)普通的動態(tài)庫,被放置在目標(biāo)系統(tǒng)的/system/lib目錄中.
除此之外,Android還包含其他的JNI庫,例如,媒體部分的JNI目錄frameworks/base/media/jni/中,被編譯成庫libmedia_jni.so.
JNI中的各個(gè)文件實(shí)際上就是C++的普通文件,其命名一般和支持的Java類有對應(yīng)關(guān)系。這種關(guān)系是習(xí)慣上的寫法,而不是強(qiáng)制的。
在Android中實(shí)現(xiàn)的JNI庫,需要連接動態(tài)庫libnativehelper.so.
2 注冊JNI方法
在Android源碼根目錄/frameworks/base/services/jni/目錄下有一個(gè)onload.cpp文件,其內(nèi)容如下:
/* * 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文件上部分為注冊函數(shù)的聲明,下部分為調(diào)用各種注冊函數(shù),而 這些注冊函數(shù)就是JNI方法的注冊函數(shù) ! 正有通過這些注冊函數(shù),上層才有可能調(diào)用注冊的JNI方法.
這些注冊函數(shù)是由同目錄下的其他.cpp文件中實(shí)現(xiàn),如上面的register_android_server_HelloService(env)這個(gè)函數(shù)是在com_android_service_HelloService.cpp文件中實(shí)現(xiàn)的.那么編譯器又是如何知道這點(diǎn)的呢? 答案當(dāng)然是Android.mk這個(gè)文件,打開這個(gè)文件,其內(nèi)容如下:
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中給出了所有實(shí)現(xiàn)文件(cpp文件)的路徑,因此編譯就能找到各個(gè)注冊函數(shù)對應(yīng)的實(shí)現(xiàn)文件了.
接下來讓我們來看看其中一個(gè)注冊函數(shù)的具體實(shí)現(xiàn)過程是如何的,比如:register_android_server_HelloService(env),打開com_android_service_HelloService.cpp文件,其下有注冊函數(shù)的實(shí)現(xiàn)代碼,如下:
int register_android_server_HelloService(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table)); }其中jniRegisterNativeMethods為注冊JNI方法函數(shù),此函數(shù)在JNI方法使用中非常重要,此函數(shù)的第二個(gè)參數(shù)為對應(yīng)著java類即HelloService.java的文件名,第三個(gè)參數(shù)為注冊的方法表:
/*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}, };接下來就是方法表內(nèi)各個(gè)接口的實(shí)現(xiàn)代碼了.
如hello_setVal函數(shù)的實(shí)現(xiàn):
/*通過硬件抽象層定義的硬件訪問接口設(shè)置硬件寄存器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的實(shí)現(xiàn)代碼中展現(xiàn)了如何調(diào)用下層HAL提供的接口, 還記得上一章: Android中HAL如何向上層提供接口總結(jié) 一文中描述HAL是如何向上層提供接口的嗎?這個(gè)hello_init函數(shù)的實(shí)現(xiàn)就是典型的調(diào)用HAL提供的初始化接口的例子,下面見hello_init這個(gè)函數(shù)的實(shí)現(xiàn)代碼:
/*通過硬件抽象層定義的硬件模塊打開接口打開硬件設(shè)備*/ 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這個(gè)open函數(shù)就是HAL提供的接口,其函數(shù)原型在hardware.h頭文件中有定義,只能返回struct hw_device_t類型的指針,而在JNI方法中,我們關(guān)心的是struct hello_device_t,只有通過struct hello_device_t,我們才能獲取其所有的成員函數(shù)(接HAL提供的接口函數(shù)),由于struct hello_device_t的第一個(gè)成員就是struct hw_device_t類型的數(shù)據(jù),因此在這里可以將獲取的struct hw_device_t強(qiáng)制轉(zhuǎn)化為struct hello_device_t來用。還沒有明白過來的,建議回過頭去看上一篇文章: Android中HAL如何向上層提供接口總結(jié) .
3 方法列表說明
關(guān)于static const JNINativeMethod method_table[]方法列表的原型如下:
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;Andoird 中使用了一種不同傳統(tǒng)Java JNI的方式來定義其native的函數(shù)。其中很重要的區(qū)別是Andorid使用了一種Java 和 C 函數(shù)的映射表數(shù)組,并在其中描述了函數(shù)的參數(shù)和返回值。這個(gè)數(shù)組的類型就是JNINativeMethod,見上述定義.
第一個(gè)變量name是Java中函數(shù)的名字。第二個(gè)變量signature,用字符串是描述了函數(shù)的參數(shù)和返回值.第三個(gè)變量fnPtr是函數(shù)指針,指向C函數(shù)。
其中比較難以理解的是第二個(gè)參數(shù),例如:"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
實(shí)際上這些字符是與函數(shù)的參數(shù)類型一一對應(yīng)的。"()" 中的字符表示參數(shù),后面的則代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
具體的每一個(gè)字符的對應(yīng)關(guān)系如下
字符 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
數(shù)組則以"["開始,用兩個(gè)字符表示
[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函數(shù)的參數(shù)是class,則以"L"開頭,以";"結(jié)尾.中間是用"/" 隔開的包及類名。而其對應(yīng)的C函數(shù)名的參數(shù)則為jobject. 一個(gè)例外是String類,其對應(yīng)的類為jstring
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
如果JAVA函數(shù)位于一個(gè)嵌入類,則用$作為類名間的分隔符。
例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
