MT6572平台加入呼吸灯功能——编写JNI

来源:互联网 发布:三丰轮廓仪怎样编程 编辑:程序博客网 时间:2024/05/21 08:02

    前面我们完成了驱动和HAL层的开发,然而仅仅这些还不足以让上层APP访问到我们的硬件设备,APP客户端界面基本上都是java语言开发的,而我们前面开发的驱动层和HAL层都是用Native语言(C/C++语言)编写的,如何让上层Java语言能够调用Native语言,这就是JNI 技术完成的。

    JNI是Java Native Interface的缩写,中文译为“Java本地调用”。JNI层的代码使用Native语言写的,通过JNI技术可以实现:Java程序中的函数可以调用Native语言写的函数,Native一般指C/C++语言编写的程序;Native程序中的函数可以调用Java层的函数。

    由于Java世界要保持它的平台无关性,所以一些硬件相关的操作就需要在Native世界实现(这部分操作在JNI层实现),这里就出现了一个问题:Java世界调用的函数是怎么与Native世界的函数关联起来的呢?如本实验中,Java类BreathLedsService.class中有两个方法是在Native世界实现的,如下:

private native void init_native();  private native void set_brightness_native(int data);
使用native关键字修饰的方法表示该方法实体是用Native语言实现的,在Java语言中可直接调用,如:

public void turnOnLeds() {      set_brightness_native(0x80);  }
这两个方法在JNI层的函数实体如下:

static void init_leds(JNIEnv* env, jobject thiz)  {      breath_leds_module_t* leds_module = NULL;            //通过hw_get_module函数查找HAL模块      if (hw_get_module(BREATH_LEDS_HW_MODULE_ID, (const hw_module_t**) &leds_module) == 0)      {          leds_ctl_open(&(leds_module->breath_module), &leds_dev);   //装载leds_dev      }  } 
static void set_brightness_leds(JNIEnv* env, jobject thiz, jint level)  {      leds_dev->set_breath_value(leds_dev, level);  }
系统怎么知道init_leds函数是init_native的实现呢?

    这其实就是JNI函数的注册问题了,JNI函数注册有两种方式:静态注册和动态注册。静态注册完全依靠名字寻找的,这种注册方法比较简单但是效率不高而且JNI层函数名特别长,写错一个字母就会导致关联出错。这里讲下动态注册,本实验就是使用动态注册的。

    既然Java native函数和JNI函数一一对应,理所当然会有一个结构来保存这种对应关系。在JNI技术中,用来记录这种一一对应关系的,是一个叫JNINativeMethod的结构,原型如下:

typedef struct {    /** Java中native函数的名字,如上面的"init_native" */    const char* name;    /** Java函数的签名信息,后面会说到 */    const char* signature;    /** JNI层对应函数的函数指针,必须是void*类型 */    void* fnPtr;} JNINativeMethod;
如何使用该结构体呢?如本例中使用如下:

static const JNINativeMethod method_tab[] = {      {"init_native", "()V", (void*) init_leds},      {"set_brightness_native", "(I)V", (void*) set_brightness_leds},  };
另外AndroidRunTime类提供了一个registerNativeMethods函数来完成注册工作,使用如下:

int register_android_server_BreathLedsService(JNIEnv *env)  {      return AndroidRuntime::registerNativeMethods(env,                     "com/android/server/BreathLedsService", method_tab, NELEM(method_tab));  }
其中第二个参数表明是这些函数在Java中的哪个类中使用。这里只是注册的实现,那么注册工作在哪调用呢?当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_Onload的函数,如果有,就调用它,而动态注册的工作就是在这里完成的。可以自己实现JNI_Onload函数,这里因为和整个系统一起编译,所以在公共加载的地方进行添加:将register_android_server_BreathLedsService函数添加进了frameworks/base/services/jni/onload.cpp文件中的JNI_OnLoad函数中。

    如上就完成的动态注册。下面来说下Java函数的签名信息,估计很多刚接触JNI的人看到上面的"()V"和"(I)V"会非常纳闷,完全不明所以。为什么要使用签名信息呢?因为Java支持函数重载,即可同名但不同参,所以仅仅根据函数名是没法找到具体函数的,Java签名信息的出现就是为了解决这个问题。JNI规范定义的函数签名信息看起来比较别扭,格式如下:

            (参数1类型标示参数2类型标示...参数n类型标示)返回值类型标示

    类型标示示意表如下:

类型标示Java类型类型标示Java类型VvoidJlongZbooleanFfloatBbyteDdoubleCcharL/java/lang/String;StringSshort[Iint[]Iint[L/java/lang/object;Object[]注意,当参数类型是引用类型时,其格式是"L包名;",其中包名中的"."换成“/”,引用类型标示最后都有一个";";如果是数组类型,则标示中会有一个"["。

    另外,关于JNI知识还有不少内容,如Java数据类型与Native类型的转换(Java中的int类型在JNI层对应转换成jint等),JNIEnv结构体中提供的一系列JNI系统函数的使用等,这里就不细说了,网上有很多资料,当然如果想追求深入,研究源代码是不二选择。下面记录下本实验JNI层开发过程。

    1)编写调用HAL模块的Service文件com_android_server_BreathLedsService.cpp

        进入frameworks/base/services/jni/目录,新建com_android_server_BreathLedsService.cpp:

#define LOG_TAG "BreathLedsService"#include "jni.h"#include "JNIHelp.h"#include "android_runtime/AndroidRuntime.h"#include <hardware/hw_breath_leds.h>#include <hardware/hardware.h>namespace android {struct breath_leds_device_t* leds_dev = NULL;  //hw_breath_leds.h中定义的HAL设备结构体static void leds_ctl_open(const struct hw_module_t* module, struct breath_leds_device_t** dev){    //调用open函数进行一系列初始化工作    module->methods->open(module, BREATH_LEDS_HW_MODULE_ID, (struct hw_device_t**) dev);}static void init_leds(JNIEnv* env, jobject thiz){    breath_leds_module_t* leds_module = NULL;        //通过hw_get_module函数查找HAL模块    if (hw_get_module(BREATH_LEDS_HW_MODULE_ID, (const hw_module_t**) &leds_module) == 0)    {        leds_ctl_open(&(leds_module->breath_module), &leds_dev);   //装载leds_dev    }}static void set_brightness_leds(JNIEnv* env, jobject thiz, jint level){    leds_dev->set_breath_value(leds_dev, level);}//定义jni函数映射static const JNINativeMethod method_tab[] = {    {"init_native", "()V", (void*) init_leds},    {"set_brightness_native", "(I)V", (void*) set_brightness_leds},};int register_android_server_BreathLedsService(JNIEnv *env){    return AndroidRuntime::registerNativeMethods(env,                   "com/android/server/BreathLedsService", method_tab, NELEM(method_tab));}}  /*  namespace android  */
调用HAL模块涉及到一个非常重要的hw_get_module函数,基本上,JNI层就是靠该函数与HAL模块产生联系,该函数可通过HAL模块.h中定义的模块ID宏找到HAL模块,并得到hw_module_t结构体,然后调用hw_module_t.hw_module_methods_t.open函数来初始化驱动。除此之外,该文件还实现了开放给Java层的接口实现set_brightness_leds。

    另外注意文件的命名方法,com_android_server前缀表示包名,表示服务类BreathLedsService放在frameworks/base/services/java目录下的com/android/server/目录下,即存在一个com.android.server.BreathLedsService类,实际上我们可以将com_android_server_BreathLedsService.cpp和下篇将要讲到的BreathLedsService.java看成一个文件,com_android_server_BreathLedsService.cpp中主要实现了BreathLedsService.java中没有实现的内容(需要使用Native语言实现)。关于BreathLedsService类我们在下篇说。

    2)修改onload.cpp,使系统启动时能自动加载上述服务

        打开frameworks/base/services/jni/onload.cpp:

        1,在namespace android { }中加入函数声明:

int register_android_server_BreathLedsService(JNIEnv* env);
        2,在extern "C" jint JNI_OnLoad(){ }中添加函数调用:

register_android_server_BreathLedsService(env);
    3)修改Android.mk,添加编译路径

        打开打开frameworks/base/services/jni/Android.mk,在LOCAL_SRC_FILES变量中添加:

com_android_server_BreathLedsService.cpp \
然后编译即可。


0 0