前面我们完成了驱动和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类型 |
V | void | J | long |
Z | boolean | F | float |
B | byte | D | double |
C | char | L/java/lang/String; | String |
S | short | [I | int[] |
I | int | [L/java/lang/object; | Object[] |
另外,关于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 \
然后编译即可。