Android下如何通過JNI方法向上提供接口總結

fmms 12年前發布 | 89K 次閱讀 Android JNI Android開發 移動開發

參考文獻:

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標準成為Java平臺的一部分,它允許java代碼和用其它語言編寫的代碼進行交互.JNI是本地編程接口,它使得在Java虛擬機(VM)內部運行的Java代碼能夠與用其他編程語言(如C,C++和匯編語言)的應用程序和庫進行交互操作.

在Android中提供的JNI的方式,讓Java程序可以調用C語言程序。Android中很多Java類都具有native接口,這些native接口就是同本地實現,然后注冊到系統中的.

JNI在Android層次結構中的作用如下圖所示:

Android下如何通過JNI方法向上提供接口總結

在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文件,其內容如下:

/*
 * 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_V*N(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_V*N(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這個文件,打開這個文件,其內容如下:

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_V*N.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文件,其下有注冊函數的實現代碼,如下:

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的文件名,第三個參數為注冊的方法表:
/*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函數的實現:

/*通過硬件抽象層定義的硬件訪問接口設置硬件寄存器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這個函數的實現代碼:
/*通過硬件抽象層定義的硬件模塊打開接口打開硬件設備*/
    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[]方法列表的原型如下:

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"

文章出處:http://blog.csdn.net/flydream0/article/details/7088514

 本文由用戶 fmms 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!