Android下如何通过JNI方法向上提供接口总结

fmms 12年前
     <p>参考文献:</p>    <p><a href="/misc/goto?guid=4959500209368267979" rel="nofollow">http://blog.csdn.net/luoshengyang/article/details/6575988</a><br /> </p>    <p><a href="/misc/goto?guid=4959500210475899817" rel="nofollow">http://www.androidmi.com/Androidkaifa/rumen/201005/633.html</a><br /> </p>    <h1>1 什么是JNI</h1>    <p>JNI是Java Native Interface的缩写,即Java本地接口.从Java1.1开始,JNI标准成为Java平台的一部分,它允许java代码和用其它语言编写的代码进行交互.JNI是本地编程接口,它使得在Java虚拟机(VM)内部运行的Java代码能够与用其他编程语言(如C,C++和汇编语言)的应用程序和库进行交互操作.</p>    <p>在Android中提供的JNI的方式,让Java程序可以调用C语言程序。Android中很多Java类都具有native接口,这些native接口就是同本地实现,然后注册到系统中的.</p>    <p>JNI在Android层次结构中的作用如下图所示:</p>    <p><img alt="Android下如何通过JNI方法向上提供接口总结" src="https://simg.open-open.com/show/39bcaf444ed4ecafde47ebfd673adbb5.gif" width="320" height="305" /><br /> </p>    <p>在Android中,主要的JNI代码在以下的路径中:</p>    <p>Android源码根目录/frameworks/base/core/jni/</p>    <p>这个路径中的内容将被编译成库libandroid_runtime.so,这就是一个普通的动态库,被放置在目标系统的/system/lib目录中.</p>    <p>除此之外,Android还包含其他的JNI库,例如,媒体部分的JNI目录frameworks/base/media/jni/中,被编译成库libmedia_jni.so.</p>    <p>JNI中的各个文件实际上就是C++的普通文件,其命名一般和支持的Java类有对应关系。这种关系是习惯上的写法,而不是强制的。</p>    <p>在Android中实现的JNI库,需要连接动态库libnativehelper.so.</p>    <h1>2 注册JNI方法</h1>    <p>在Android源码根目录/frameworks/base/services/jni/目录下有一个onload.cpp文件,其内容如下:</p>    <pre class="brush:cpp; toolbar: true; auto-links: false;">/*  * 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; }</pre>    <p></p> onload.cpp文件上部分为注册函数的声明,下部分为调用各种注册函数,而    <span style="color:#ff0000;">这些注册函数就是JNI方法的注册函数</span>! 正有通过这些注册函数,上层才有可能调用注册的JNI方法.    <p></p>    <p>这些注册函数是由同目录下的其他.cpp文件中实现,如上面的 register_android_server_HelloService(env)这个函数是在 com_android_service_HelloService.cpp文件中实现的.那么编译器又是如何知道这点的呢? 答案当然是Android.mk这个文件,打开这个文件,其内容如下:</p>    <pre class="brush:shell; toolbar: true; auto-links: false;">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)</pre>    <p>在LOCAL_SRC_FILE中给出了所有实现文件(cpp文件)的路径,因此编译就能找到各个注册函数对应的实现文件了. </p>    <p></p>    <p>接下来让我们来看看其中一个注册函数的具体实现过程是如何的,比如:register_android_server_HelloService(env),打开 com_android_service_HelloService.cpp文件,其下有注册函数的实现代码,如下:</p>    <pre class="brush:cpp; toolbar: true; auto-links: false;">int register_android_server_HelloService(JNIEnv *env) {      return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table)); }</pre>    <p></p> 其中jniRegisterNativeMethods为注册JNI方法函数,此函数在JNI方法使用中非常重要,此函数的第二个参数为对应着java类即HelloService.java的文件名,第三个参数为注册的方法表:    <pre class="brush:cpp; toolbar: true; auto-links: false;">/*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}, };</pre>    <p></p> 接下来就是方法表内各个接口的实现代码了.    <p></p>    <p>如hello_setVal函数的实现:</p>    <pre class="brush:cpp; toolbar: true; auto-links: false;">/*通过硬件抽象层定义的硬件访问接口设置硬件寄存器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); }</pre>    <p></p> 方法列表中的hello_init的实现代码中展现了如何调用下层HAL提供的接口, 还记得上一章:    <a href="/misc/goto?guid=4959500210617074796" rel="nofollow">Android中HAL如何向上层提供接口总结</a> 一文中描述HAL是如何向上层提供接口的吗?这个hello_init函数的实现就是典型的调用HAL提供的初始化接口的例子,下面见hello_init这个函数的实现代码:    <pre class="brush:cpp; toolbar: true; auto-links: false;">/*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/  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;    }</pre>    <p></p> 上述的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来用。还没有明白过来的,建议回过头去看上一篇文章:    <a href="/misc/goto?guid=4959500209453179994" rel="nofollow">Android中HAL如何向上层提供接口总结</a> .    <h1>3 方法列表说明<br /> </h1>    <p>关于static const JNINativeMethod method_table[]方法列表的原型如下:</p>    <pre class="brush:cpp; toolbar: true; auto-links: false;">typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;</pre>    <p></p> Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。其中很重要的区别是Andorid使用了一种Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型就是JNINativeMethod,见上述定义.    <p></p>    <p></p>    <p>第一个变量name是Java中函数的名字。第二个变量signature,用字符串是描述了函数的参数和返回值.第三个变量fnPtr是函数指针,指向C函数。</p> 其中比较难以理解的是第二个参数,例如:    <p>"()V"</p>    <p>"(II)V"</p>    <p>"(Ljava/lang/String;Ljava/lang/String;)V"</p> 实际上这些字符是与函数的参数类型一一对应的。    <p>"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();</p>    <p>"(II)V" 表示 void Func(int, int);</p>    <p><br /> 具体的每一个字符的对应关系如下<br /> 字符 Java类型    C类型</p>    <p>V      void              void<br /> Z       jboolean     boolean<br /> I        jint                int<br /> J       jlong            long<br /> D      jdouble       double<br /> F      jfloat            float<br /> B      jbyte            byte<br /> C      jchar           char<br /> S      jshort          short</p>    <p><br /> 数组则以"["开始,用两个字符表示</p>    <p><br /> [I       jintArray      int[]<br /> [F     jfloatArray    float[]<br /> [B     jbyteArray    byte[]<br /> [C    jcharArray    char[]<br /> [S    jshortArray   short[]<br /> [D    jdoubleArray double[]<br /> [J     jlongArray     long[]<br /> [Z    jbooleanArray boolean[]</p>    <p><br /> 上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾.中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring</p>    <p><br /> Ljava/lang/String;   String jstring<br /> Ljava/net/Socket;   Socket jobject</p>    <p><br /> 如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。</p>    <p>例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"</p>    <p>文章出处:<a href="/misc/goto?guid=4959500210726747148" rel="nofollow">http://blog.csdn.net/flydream0/article/details/7088514</a></p>