Android系统HAL驱动开发经典案例详解(基于Android4.0)
目的:通过学习一个LED点灯的简单功能,掌握Linux驱动程序与HAL硬件抽象层之间的调用方法,同时掌握JNI层的编写思想,学会使用Eclipse编写Android应用程序,深入体会Android HAL架构。本章内容主要参考文献:《Android深度探索(卷1) HAL与驱动开发》、《TQ210开发板Android_HAL_LED_V1.2.pdf》
目录:
一、架构分析
1.1 功能介绍
1.2 HAL架构
1.3 接口定义
二、驱动程序
2.1 驱动功能简介
2.2 驱动源码分析
2.3 利用可执行文件测试驱动
2.3.1 源文件
2.3.2 利用交叉编译器编译
2.3.3 使用原生C程序测试驱动
三、HAL硬件抽象层
3.1 HAL简介
3.2 HAL层源码与分析
3.3 HAL源码编译
四、Jni/Service服务层
4.1 直接使用JNI调用驱动程序
4.2 JNI/Service程序代码
五、APP应用程序层
5.1 activity_main.xml
5.2 LedServer.java
5.3 MainActivity.java
一、架构分析
1.1 功能介绍
Linux版本:3.0.8
Android版本:4.0
开发板:FriendlyARM smart210
功能介绍:应用程序界面如下图所示,为求方便,截图来自模拟器,实际开发板界面和下图是一样的。CheckBox复选框分别对应开发板上四个LED灯,选中复选框,按动led_hal_jni按键后,相应LED就会亮起来。功能很简单,代码中几乎不会涉及任何逻辑算法,主要是为了方便理清整个程序架构,体会Android系统通过HAL硬件抽象层与实际硬件之间交互的编程思想。
1.2 HAL架构
想要点亮一个灯当然简单,可以直接烧写ARM裸机程序,也可以在跑Linux系统的板子上通过写/dev/leds这样的设备文件来达到目的。Android系统为了解决一些调用接口和版权方面的问题提出了一个HAL架构。目前最新的HAL架构是下面这个样子的。
可以看出,Android应用程序直接调用的是JNI或者Service程序库文件(.so),程序库文件是通过一个ID来定位到相应的HAL的库文件(.so),最终和硬件打交道的是HAL模块。需要注意的是,HAL及其以上层均属于应用程序范畴,也就是都运行在了用户空间,只有Linux驱动程序运行在了Linux内核空间。
1.3 接口定义
从Android HAL架构来看,一套完整的点灯的程序应该包括四个层次。为了方便程序维护,层与层之间应该尽可能的降低耦合程度,每个层对其他层开放的仅仅是一个操作接口即可。每个层的源文件位置和其向其他层提供的关键接口表述如下,这里指的关键接口实际上就是“调用函数”。各个源文件的具体内容会在下文给出。如果你能亲自动手写完整套程序再回过头来看这部分接口定义,应该能体会得更深些。
1、底层驱动。
源文件位置:$(linux_source)/drivers/char/mini210_led.c
对应开发板上的驱动设备文件为:/dev/leds
关键接口定义:
//发送IO命令
ioctl(file_handler, cmd, arg);
//调用方法如下
./ioctl_test /dev/leds 1 2 //表示点亮第二盏灯
//发送写命令
write(fd,buf,strlen(buf));
//调用方法如下
./write_test /dev/s3c6410_leds "1111" // 点亮所有灯
这里的ioctl_test和write_test是两个可执行文件,它们分别是由ioctl_test.c和write_test.c源文件通过交叉编译器或Android原生代码编译出来的,这两个源文件是为了验证驱动程序专门写的,后文会贴出具体代码。
2、HAL层。
源文件位置:
//这个.c文件也可以放到其他目录
$(Android_source)/device/friendly-arm/mini210/libled/led_hal.c
//这个.h文件最好放到指定位置,以免包含头文件的时候发生错误
$(Android_source)/hardware/libhardware/include/hardware/led_hal.h
生成模块位置:Android_source/out/target/product/mini210/lib/hw/led_hal.default.so
关键接口定义:
int led_on(struct led_control_device_t *dev, int32_t LED_NUMBER) //表示LED_NUMBER灯亮
{
... ...
ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER);
... ...
}
int led_off(struct led_control_device *dev, int32_t LED_NUMBER) //表示LED_NUMBER灯灭
{
... ...
ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER);
... ...
}
3、jni/service层
源文件位置:$(Android_source)/packages/apps/led/jni/LedHalService.cpp
生成模块位置:Android_source/out/target/product/mini210/lib/libled_hal_jni.so
关键接口定义:
jboolean Java_com_example_leds_MainActivity_ledSetOn (JNIEnv* env, jobject obj,jint number)
{
... ...
return sLedDevice->set_on(sLedDevice,number);
... ...
}
jboolean Java_com_example_leds_MainActivity_ledSetOff (JNIEnv* env, jobject obj,jint number)
{
... ...
return sLedDevice->set_off(sLedDevice,number);
... ...
}
类名定义:
//注意这里的kClassName决定了调用该JNI模块的应用程序的包名与类名。
int register_android_server_LedService(JNIEnv *env)
{
... ...
static const char* const kClassName = "com/example/leds/LedService";
... ...
}
二、驱动程序
2.1 驱动功能简介
mini210_led.c主要提供了mini210_leds_ioctl和mini210_leds_write两个接口。应用程序通过向/dev/leds发送I/O指令或写命令就可以实现控制LED灯的功能了。
2.2 驱动源码分析
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "leds"
static unsigned char mem[4];
static int led_gpios[] = {
S5PV210_GPJ2(0),
S5PV210_GPJ2(1),
S5PV210_GPJ2(2),
S5PV210_GPJ2(3),
};
#define LED_NUM ARRAY_SIZE(led_gpios)
//mini210_leds_ioctl函数实现了发送IO控制命令
static long mini210_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case 0:
case 1:
if (arg > LED_NUM) {
return -EINVAL;
}
gpio_set_value(led_gpios[arg], !cmd);
//printk(DEVICE_NAME": %d %d\n", arg, cmd);
break;
default:
return -EINVAL;
}
return 0;
}
//mini210_leds_write函数实现了写文件命令
ssize_t mini210_leds_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int i=0;
int tmp=count;
memset(mem,0,4);
if(count>4)
{
tmp=4;
}
if(copy_from_user(mem,buf,tmp))
{
return -EFAULT;
}
for(i=0;i<4;i++)
{
if(mem[i]=='1')
{
gpio_set_value(led_gpios[i], 0);
}
else if(mem[i]=='0')
{
gpio_set_value(led_gpios[i], 1);
}
}
return count;
}
static struct file_operations mini210_led_dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = mini210_leds_ioctl,
.write = mini210_leds_write,
};
static struct miscdevice mini210_led_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &mini210_led_dev_fops,
};
static int __init mini210_led_dev_init(void) {
int ret;
int i;
for (i = 0; i < LED_NUM; i++) {
ret = gpio_request(led_gpios[i], "LED");
if (ret) {
printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
led_gpios[i], ret);
return ret;
}
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
gpio_set_value(led_gpios[i], 1);
}
ret = misc_register(&mini210_led_dev);
printk(DEVICE_NAME"\tinitialized\n");
return ret;
}
static void __exit mini210_led_dev_exit(void) {
int i;
for (i = 0; i < LED_NUM; i++) {
gpio_free(led_gpios[i]);
}
misc_deregister(&mini210_led_dev);
}
module_init(mini210_led_dev_init);
module_exit(mini210_led_dev_exit);
MODULE_LICENSE("GPL");
2.3 利用可执行文件测试驱动
2.3.1 源文件
首先给出两个可执行文件的源文件
//ioctl_test.c
#include
#include
#include
#include
/*
* ./ioctl_test /dev/leds 1 2 //表示将第二个灯点亮
*/
int main(int argc, char **argv)
{
int file_handler = 0;
int cmd = 0;
int arg = 0;
if(argc < 4)
{
printf("Usage: ioctl \n");
return 0;
}
cmd = atoi(argv[2]);
arg = atoi(argv[3]);
printf("dev:%s\n", argv[1]);
printf("cmd:%d\n", cmd);
printf("arg:%d\n", arg);
file_handler = open(argv[1], 0);
ioctl(file_handler, cmd, arg);
close(file_handler);
return 0;
}
//write_test.c
#include
#include
#include
#include
#include
#include
/*
* ./write_test /dev/s3c6410_leds "1111" 表示四个灯全亮
*/
void main(int argc, char *argv[])
{
int fd;
char *buf;
buf=argv[2];
fd=open(argv[1],O_WRONLY);
if(fd<0)
{
printf("file_open failed!\n");
return;
}
write(fd,buf,strlen(buf));
close(fd);
}
源文件的内容比较简单,不再深入讨论。Android系统有两种编译可执行程序的方法,下面分别介绍。
2.3.2 利用交叉编译器编译
这种方法比较容易实现。直接在命令行模式下编译即可。需要注意的是最好加上静态编译选项“-static”。
Pc:#arm-linux-gcc -static ioctl_test.c -o ioctl_test
Pc:#arm-linux-gcc -static write_test.c -o write_test
然后利用adb命令将可执行文件上传到开发板
Pc:# adb push ioctl_test write_test /data/local
进入开发板的命令行终端
Pc:# adb shell
然后就可以执行可执行文件了
Phone#: cd /data/local
Phone:# ./ioctl_test /dev/leds 1 2
Phone:#./write_test /dev/leds “1111”
2.3.3 使用原生C程序测试驱动
这种方式更加地道,其编译方式是通过Android源代码直接编译的。所使用的工具就是Android系统自带的编译器以及一些Android系统的头文件,所以编译之前一定确保自己的电脑上已经成功编译了一套Android系统。至于如何编译Android系统,网上有很多教程,难点就是编译的过程中会出现一些莫名其妙的错误,往往是少安装了某些库造成的,耐着性子上网查查一般就能解决。
接下来以ioctl_test为例讲解如何使用原生C程序测试驱动。首先一定要在Android系统源代码目录或子目录下为ioctl_test应用程序单独建立一个文件夹,作为其主目录。然后在ioctrl_test.c的主目录下新建一个Android.mk文件,内容如下:
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=ioctl_test.c
LOCAL_MODULE:=ioctl_test
LOCAL_MODULE_TAGS:=optional
include $(BUILD_EXECUTABLE)
Android.mk其实就相当与Android系统中的Makefile文档,就是给出了一些编译选项,需要执行mm命令或mmm命令进行编译。关于Android.mk的书写规则,网上有很多,看看就知道了。
在使用mm命令之前,一定确保首先在Android系统源代码下执行:
Pc:# source ./build/envsetup.sh 执行后会出现类似下面的信息。
including device/friendly-arm/mini210/vendorsetup.sh
including device/moto/stingray/vendorsetup.sh
including device/moto/wingray/vendorsetup.sh
including device/samsung/crespo4g/vendorsetup.sh
including device/samsung/crespo/vendorsetup.sh
including device/samsung/maguro/vendorsetup.sh
including device/samsung/toro/vendorsetup.sh
including device/samsung/tuna/vendorsetup.sh
including device/ti/panda/vendorsetup.sh
including sdk/bash_completion/adb.bash
这个envsetup.sh就是包含自定义的vendorsetup.sh和初始化mm等命令用的(插一句题外话,如果你想从官方的Android源码定制适合自己开发板的Android系统的话,通常需要自己写一个新的vendorsetup.sh文件)。注意的是,每次启动一个新的Linux命令终端,好像都要重新运行source ./build/envsetup.sh,否则mm命令不能用。
然后执行lunch
Pc:# lunch
You're building on Linux
Lunch menu... pick a combo:
1. full-eng
2. full_x86-eng
3. vbox_x86-eng
4. full_mini210-userdebug
5. full_stingray-userdebug
6. full_wingray-userdebug
7. full_crespo4g-userdebug
8. full_crespo-userdebug
9. full_maguro-userdebug
10. full_toro-userdebug
11. full_tuna-userdebug
12. full_panda-eng
Which would you like? [full-eng] 4
在这里选择4。同样的,每次运行完source ./build/envsetup.sh之后也要重新运行lunch命令,否则系统仍会默认以full-eng的方式进行编译。当你编译ioctl_test.c这样的Android应用程序的时候就会出现类似下面的错误。
make: Entering directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'
make: *** No rule to make target `out/target/product/generic/obj/lib/crtbegin_dynamic.o', needed by `out/target/product/generic/obj/EXECUTABLES/ioctl_test_intermediates/LINKED/ioctl_test'. Stop.
make: Leaving directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'
一定要选择full_mini210-userdebug。然后会出现下面的信息。
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=4.0.3
TARGET_PRODUCT=full_mini210
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a-neon
HOST_ARCH=x86
HOST_OS=linux
HOST_BUILD_TYPE=release
BUILD_ID=IML74K
============================================
好了,最后然后进入到ioctl_test.c的主目录中执行:
Pc:#mm
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=4.0.3
TARGET_PRODUCT=full_mini210
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a-neon
HOST_ARCH=x86
HOST_OS=linux
HOST_BUILD_TYPE=release
BUILD_ID=IML74K
============================================
make: Entering directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'
target thumb C: ioctl_test <= /work/MySystem/3_Android_resource/android-4.0.3_r1/ioctl_test/ioctl_test.c
target Executable: ioctl_test (out/target/product/mini210/obj/EXECUTABLES/ioctl_test_intermediates/LINKED/ioctl_test)
target Symbolic: ioctl_test (out/target/product/mini210/symbols/system/bin/ioctl_test)
target Strip: ioctl_test (out/target/product/mini210/obj/EXECUTABLES/ioctl_test_intermediates/ioctl_test)
Install: out/target/product/mini210/system/bin/ioctl_test
make: Leaving directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'
从编译结果可以看出,编译出的可执行文件ioctl_test被放在了out/target/product/mini210/system/bin/文件下。按照2.3.2的方法将其上传到开发板后即可使用。
三、HAL硬件抽象层
3.1 HAL简介
Google为Android加入HAL主要有如下目的。
Ø 统一硬件的调用接口。由于HAL有标准的调用接口,可以利用HAL屏蔽Linux驱动复杂、不统一的接口。
Ø 解决了GPL版权问题。Android系统基于Apache Licence2.0协议。可以让那些不想开源的驱动作者屏蔽源代码。这是基于GPL开源协议的Linux系统所不允许的。所以Linux毫不客气的将Android系统开除族籍了。
Ø 访问用户空间资源。对于有些硬件,可能需要访问一些用户空间的资源,或在内核空间不方便完成的工作以及特殊需要。在这种情况下,可以利用位于用户空间的HAL代码来辅助Linux驱动完成一些工作。
在编写HAL层程序的时候要时刻注意HAL是运行在用户空间的,这有助于理解HAL层的设计思想。HAL(硬件抽象层)的诞生意味着Android系统被Linux大家族给剔除了。原因其实就是HAL层可以不开源。
3.2 HAL层源码与分析
撰写HAL程序关键之处在于3个重要的结构体。分别是描述HAL模块的hw_module_t结构体;描述HAL设备的hw_device_t结构体;描述模块入口函数的hw_module_methods_t结构体。
首先给出头文件led_hal.h
//led_hal.h
#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
__BEGIN_DECLS
/*下面这个ID重要的很,Jni/Service层主要通过它来寻找相应的HAL库文件(.so),所以HAL源文件的名字是可以随便改的,只要保证其中的ID值不变即可*/
#define LED_HARDWARE_MODULE_ID "led_hal"
/*HAL规定不能直接使用hw_module_t结构体,需要在hw_modult_t外再套一层结构体。hw_module_t结构体表示HAL模块的相关信息,成员变量可以随便起,但是hw_module_t结构体必须是led_module_t结构体的第1个成员变量的数据类型。这个东西是整个HAL的核心,后续的工作其实就是不断的完善这个module。*/
struct led_module_t
{
struct hw_module_t common;
};
#define IOCTL_GPIO_ON 1
#define IOCTL_GPIO_OFF 0
/*led_control_device_t是自定义的hw_device_t类型的结构体,但是hw_device_t必须是该结构体的第一个成员变量,另外还定义了两个控制LED的成员函数*/
struct led_control_device_t
{
struct hw_device_t common;
int (*set_on)(struct led_control_device_t* dev, int32_t LED_NUMBER);
int (*set_off)(struct led_control_device_t* dev, int32_t LED_NUMBER);
};
__END_DECLS
#endif //ANDROID_LED_INTERFACE_H
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Led_hal.c文件是HAL层的源文件,我在每个函数前都做了注解,其编号是按照代码书写顺序标注的。也就是说,HAL代码的编写顺序并不是自上而下一行一行写出来的,而是类似于驱动程序的形式,先从接口程序开始写,然后一层一层的把用到的函数补充出来。
//Led_hal.c
#include
char const * const LED_DEVICE = "/dev/leds";
static int fd = -1;
/*4、打开设备文件,这里的open函数就是运行在了用户空间*/
static int open_led()
{
if((fd = open(LED_DEVICE,O_RDWR))==-1)
{
LOGV("LED stub: open %s failed\n",LED_DEVICE);
return -1;
}
else
{LOGV("LED stub: open %s successed\n",LED_DEVICE);}
return 1;
}
/*5、关闭设备,没什么说的*/
static int close_led(struct hw_device_t *dev)
{
LOGV("close_light is called");
if(fd!=-1)
{
close(fd);
if(dev)
free(dev);
}
return 0;
}
/*6、ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER)运行在了用户空间*/
int led_on(struct led_control_device_t *dev, int32_t LED_NUMBER)
{
if(fd == -1)
return -1;
return ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER);
}
/*7、ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER)运行在了用户空间*/
int led_off(struct led_control_device *dev, int32_t LED_NUMBER)
{
if(fd == -1)
return -1;
return ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER);
}
/*3、初始化设备文件*/
static int led_init(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
{
struct led_control_device_t *dev;
/*为led_control_device结构体分配内存空间*/
dev = (struct led_control_device_t *)malloc(sizeof(*dev));
if(dev==NULL)
return 0;
memset(dev,0,sizeof(*dev));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*)module;
//dev->common.close = (int (*)(struct hw_device_t *))close_led;
dev->common.close = close_led;
//设置打开LED的函数指针
dev->set_on = led_on;
//设置关闭LED的函数指针
dev->set_off = led_off;
*device = (struct hw_device_t *)&dev->common;
if(open_led() == -1)
{
free(dev);
dev = NULL;
return -1;
}
return 0;
}
/*2、hw_module_methods_t中定义了打开设备的open函数的指针。也就是在这个函数中进行了一些列的初始化工作*/
static struct hw_module_methods_t led_module_methods={
open: led_init
};
/*1、HAL_MODULE_INFO_SYM才是HAL真正的入口,这里面最重要的两个参数是id: LED_HARDWARE_MODULE_ID和methods: &led_module_methods*/
struct led_module_t HAL_MODULE_INFO_SYM={
common:
{
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: LED_HARDWARE_MODULE_ID,
name: "sample led hal stub",
author: "xxx",
methods: &led_module_methods,
}
};
3.3 HAL源码编译
//Android.mk
LOCAL_PATH:= $(call my-dir)
# HAL module implemenation stored in
# hw/..so
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
LOCAL_SRC_FILES := led_hal.c
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
#LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE := led_hal.default
#LOCAL_MODULE := led_hal.$(TARGET_BOARD_PLATFORM)
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)
编译出来的led_hal.default.so模块保存在了$(Android_source)/out/target/produce/mini210/system/lib/hw中。将之上传到开发板中相应的/system/lib/hw文件夹中即可。
四、Jni/Service服务层
这一层的主要任务就是调用HAL层的程序库,并为上层应用程序层提供调用接口。
4.1 直接使用JNI调用驱动程序
在Java中是可以写C/C++代码的,虽然并不是100%的C/C++语言,但只需要改动一下数据类型的标志并注意一些新定义的用法即可。我们常把C/C++代码称之为原生代码,如果不是特别讲究叫法的话,在Java体系中还可以勉为其难的称之为JNI或NDK,其实JNI和NDK意义并不同,首先介绍一下JNI和NDK的区别。
JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。
NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
所以可以简单的理解为JNI是接口,NDK是工具。
在涉及到操作硬件的思路上,我们其实可以直接通过Java程序去调用驱动程序,Java程序提供了File.write(str)这样的写函数操作类,所以可以直接向/dev/leds设备驱动文件中写数据。不过Java并没有提供ioctl这样的发送IO控制命令的函数。如果就想发送IO控制命令该怎么办?可以通过JNI层来调用C/C++语言实现。
先给出完成后的操作界面。
然后直接贴出各个源文件。
//activity_main.xml
// jni/leds.c
#include
#include
#include
#include
#include
#include
#include
#include
jstring Java_com_example_leds1_MainActivity_getText (JNIEnv* env, jobject obj)
{
return (*env)->NewStringUTF(env, "Test Android NDK!Test My NDK!");
}
char* jstring_to_pchar(JNIEnv* env, jstring str)
{
char* pstr = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env, "utf-8");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B");
jbyteArray byteArray = (jbyteArray)(
(*env)->CallObjectMethod(env, str, mid, strencode));
jsize size = (*env)->GetArrayLength(env, byteArray);
jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
if (size > 0)
{
pstr = (char*) malloc(size);
memcpy(pstr, pbyte, size);
}
return pstr;
}
void Java_com_example_leds1_MainActivity_writeLeds(JNIEnv* env, jobject thiz, jstring str)
{
int fd;
fd = open("/dev/leds",O_RDWR);
char* pstr = jstring_to_pchar(env,str);
write(fd,pstr,strlen(pstr));
close(fd);
}
void Java_com_example_leds1_MainActivity_ioctlLeds(JNIEnv* env, jobject thiz, jint cmd, jint arg)
{
int fd;
fd = open("/dev/leds",O_RDWR);
ioctl(fd,cmd,arg);
close(fd);
}
//jni/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := leds
LOCAL_SRC_FILES := leds.c
include $(BUILD_SHARED_LIBRARY)
//MainActivity.java
package com.example.leds1;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.CheckBox;
public class MainActivity extends Activity {
private CheckBox[] cbStrLeds = new CheckBox[4];
private CheckBox[] cbCmdLeds = new CheckBox[4];
TextView textView;
public native String getText();//声明native 方法
public native void writeLeds(String str);
public native void ioctlLeds(int cmd, int arg);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cbStrLeds[0] = (CheckBox)findViewById(R.id.checkbox_str_led1);
cbStrLeds[1] = (CheckBox)findViewById(R.id.checkbox_str_led2);
cbStrLeds[2] = (CheckBox)findViewById(R.id.checkbox_str_led3);
cbStrLeds[3] = (CheckBox)findViewById(R.id.checkbox_str_led4);
cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);
cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);
cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);
cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);
String myString = getText();//调用native方法
// long z = add(2, 3);
// String zstr=Long.toString(z);
textView = (TextView)findViewById(R.id.textView);
textView.setText(myString);
}
public void onClick_write(View view)
{
String str="";
for(int i=0;i<4;i++)
{
if(cbStrLeds[i].isChecked())
str +='1';
else
str +='0';
}
TextView textview = (TextView)findViewById(R.id.textView);
textview.setText(str);
writeLeds(str);
}
public void onClick_ioctl(View view)
{
TextView textview = (TextView)findViewById(R.id.textView);
textview.setText("ioctl_test!");
for(int i=0;i<4;i++)
{
if(cbCmdLeds[i].isChecked())
ioctlLeds(1,i);
else
ioctlLeds(0,i);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
static {
System.loadLibrary("leds"); //导入链接库
}
}
需要注意的是,想要编译jni中的文件,必须在电脑上提前装好ndk,这也是一个非常客观的工程量。然后重点是,需要在命令行或者界面形式下编译jni,具体方法上网查查吧。
4.2 JNI/Service程序代码
//LedHalService.cpp
#include
#include
#include
#include
#include //用到了和HAL层同一个LED_HARDWARE_MODULE_ID = led_hal
#ifdef __cplusplus
extern "C" {
#endif
struct led_control_device_t *sLedDevice = NULL;
//open the led device by hal
static inline int led_control_open(struct hw_module_t *module,struct led_control_device_t **device)
{
return module->methods->open(module,LED_HARDWARE_MODULE_ID,(struct hw_device_t**)device);
}
//close the led device by hal
jboolean Java_com_example_leds_MainActivity_ledClose (JNIEnv* env, jobject obj)
{
if(sLedDevice)
{
sLedDevice->common.close(&(sLedDevice->common));
}
return 0;
}
//turn on the led
jboolean Java_com_example_leds_MainActivity_ledSetOn (JNIEnv* env, jobject obj,jint number)
{
if(sLedDevice)
{
return sLedDevice->set_on(sLedDevice,number);
}
return false;
}
//turn off the led
jboolean Java_com_example_leds_MainActivity_ledSetOff (JNIEnv* env, jobject obj,jint number)
{
if(sLedDevice)
{
return sLedDevice->set_off(sLedDevice,number);
}
return false;
}
//led init
jboolean Java_com_example_leds_MainActivity_ledInit (JNIEnv* env, jobject obj)
{
led_module_t *module;
/*看到了吗,hw_get_module是调用HAL模块的核心函数,其中的LED_HARDWARE_MODULE_ID就是前文一直在提的ID号,JNI模块就是通过这个ID号找到的/system/lib/hw文件中相应的HAL共享库模块*/
int err = hw_get_module(LED_HARDWARE_MODULE_ID,(hw_module_t const**)&module);
if(err == 0)
{
if(led_control_open(&(module->common),&sLedDevice) == 0)
return true;
}
sLedDevice = NULL;
return false;
}
static led_control_device_t * get_device(hw_module_t* module, char const* name)
{
int err;
hw_device_t* device;
err = module->methods->open(module,name,&device);
if(err == 0)
{
return (led_control_device_t*)device;
}
else
{
return NULL;
}
}
/*通过JNINativeMethod数组定义了函数映射表,注册给Java虚拟机,这样JVM就可以用函数映射表来调用相应的函数,该方式属于动态调用共享库*/
static JNINativeMethod method_table[]={
{"led_init","()Z",(void*)Java_com_example_leds_MainActivity_ledInit},
{"led_setOn","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOn},
{"led_setOff","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOff},
{"led_close","()Z",(void*)Java_com_example_leds_MainActivity_ledClose},
};
int register_android_server_LedService(JNIEnv *env)
{
//This sentence is very important because we will use this class to call the service
static const char* const kClassName = "com/example/leds/LedService";
//static const char* const kClassName = "com/example/leds";
jclass clazz;
/*Look up the class*/
clazz = env->FindClass(kClassName);
if(clazz == NULL)
{
return -1;
}
/*Register all the methods*/
if(env->RegisterNatives(clazz,method_table,sizeof(method_table)/sizeof(method_table[0]))!=JNI_OK)
{
return -1;
}
/*Fill out the rest of the ID cache*/
return 0;
}
/*系统在成功装在JNI共享库后会自动调用JNI_OnLoad函数,该函数一般用与初始化JNI模块*/
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if(vm->GetEnv((void**) &env,JNI_VERSION_1_4)!=JNI_OK)
{
return result;
}
/*使用下面的函数绑定JNI程序库与Java程序库。若想使用静态调用libled_hal_jni.so,则需要将下面的代码屏蔽掉*/
register_android_server_LedService(env);
/*返回JNI_VERSION_1_4,表明只有运行在JDK1.4及以上版本的Java程序才能调用当前的JNI模块*/
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif
//Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE :=false
LOCAL_MODULE_TAGS:=optional
LOCAL_SRC_FILES:=LedHalService.cpp
LOCAL_MODULE:=libled_hal_jni
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libcutils \
libhardware \
libhardware_legacy \
libnativehelper \
libsystem_server \
libutils \
libui \
libsurfaceflinger_client
include $(BUILD_SHARED_LIBRARY)
五、APP应用程序层
关于如何在APP中调用JNI库文件,上文中4.1节有提到,不过那是在工程中建立了一个jni文件夹,生成的JNI的库文件直接保存到了当前工程的lib文件夹下。当然也可以不用在当前工程下建立jni文件夹,像4.2那样通过Android原生代码进行编译,然后直接通过绝对路径加载模块也能产生相同功能。
5.1 activity_main.xml
5.2 LedServer.java
package com.example.leds;
public class LedService {
private static LedService LedService;
public static LedService getInstance()
{
if (LedService == null)
LedService = new LedService();
return LedService;
}
private LedService()
{
init();
}
public boolean init()
{
return led_init();
}
public boolean setOn(int led)
{
return led_setOn(led);
}
public boolean setOff(int led)
{
return led_setOff(led);
}
// native method
private native boolean led_init();
private native boolean led_setOn(int led);
private native boolean led_setOff(int led);
static
{
System.load("/system/lib/libled_hal_jni.so");
}
}
5.3 MainActivity.java
package com.example.leds;
import com.example.leds.LedService;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
public class MainActivity extends Activity {
private CheckBox[] cbCmdLeds = new CheckBox[4];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);
cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);
cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);
cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);
/*
LedService ledService = LedService.getInstance();
ledService.setOn(0);
ledService.setOff(1);
ledService.setOn(2);
ledService.setOff(3);
*/
}
public void onClick_led(View view)
{
LedService ledService = LedService.getInstance();
for(int i=0;i<4;i++)
{
if(cbCmdLeds[i].isChecked())
ledService.setOn(i);
else
ledService.setOff(i);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
好了,关于整套程序都已经写完了。