1. 程式人生 > >Android下如何通過JNI方法向上提供介面總結

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”