前言
意外在网上发现了这扁文章,看后感觉很有必要分享,所以整理并上传,希望大家喜欢。
Android 硬件抽象层(HAL)概要介绍和学习计划
Android 的硬件抽象层,简单来说,就是对Linux 内核驱动程序的封装,向上提供接口,屏蔽低层的实现细节。也就是说,把
对硬件的支持分成了两层,一层放在用户空间(User Space),一层放在内核空间(Kernel Space),其中,硬件抽象层运行在
用户空间,而Linux 内核驱动程序运行在内核空间。为什么要这样安排呢?把硬件抽象层和内核驱动整合在一起放在内核空间
不可行吗?从技术实现的角度来看,是可以的,然而从商业的角度来看,把对硬件的支持逻辑都放在内核空间,可能会损害
厂家的利益。我们知道,Linux 内核源代码版权遵循GNU License,而Android 源代码版权遵循Apache License,前者在发布产
品时,必须公布源代码,而后者无须发布源代码。如果把对硬件支持的所有代码都放在Linux 驱动层,那就意味着发布时要公
开驱动程序的源代码,而公开源代码就意味着把硬件的相关参数和实现都公开了,在手机市场竞争激烈的今天,这对厂家来
说,损害是非常大的。因此,Android 才会想到把对硬件的支持分成硬件抽象层和内核驱动层,内核驱动层只提供简单的访问
硬件逻辑,例如读写硬件寄存器的通道,至于从硬件中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中
去了,这样就可以把商业秘密隐藏起来了。也正是由于这个分层的原因,Android 被踢出了Linux 内核主线代码树中。大家想
想,Android 放在内核空间的驱动程序对硬件的支持是不完整的,把Linux 内核移植到别的机器上去时,由于缺乏硬件抽象层
的支持,硬件就完全不能用了,这也是为什么说Android 是开放系统而不是开源系统的原因。撇开这些争论,学习Android 硬
件抽象层,对理解整个Android 整个系统,都是极其有用的,因为它从下到上涉及到了Android 系统的硬件驱动层、硬件抽象
层、运行时库和应用程序框架层等等,下面这个图阐述了硬件抽象层在Android 系统中的位置,以及它和其它层的关系:
在学习Android 硬件抽象层的过程中,我们将会学习如何在内核空间编写硬件
驱动程序、如何在硬件抽象层中添加接口支持访问硬件、如何在系统启动时
提供硬件访问服务以及 如何编写JNI 使得可以通过Java 接口来访问硬件,而
作为中间的一个小插曲,我们还将学习一下如何在Android 系统中添加一个C
可执行程序来访问硬件驱动程序。由于这是一个系统的学习过程,笔者将分
成六篇文章来描述每一个学习过程,包括:
一. 在Android 内核源代码工程中编写硬件驱动程序。
二. 在Android 系统中增加C 可执行程序来访问硬件驱动程序。
三. 在Android 硬件抽象层增加接口模块访问硬件驱动程序。
四. 在Android 系统中编写JNI 方法在应用程序框架层提供Java 接口访问硬件。
五. 在Android 系统的应用程序框架层增加硬件服务接口。
六. 在Android 系统中编写APP 通过应用程序框架层访问硬件服务。
学习完这六篇文章,相信大家对Android 系统就会有一个更深刻的认识了,敬请关注。
在Ubuntu 上为Android 系统编写Linux 内核驱动程序
这里,我们不会为真实的硬件设备编写内核驱动程序。为了方便描述为Android 系统编写内核驱动程序的过程,我们使用一
个虚拟的硬件设备,这个设备只有一个4 字节的寄存器,它可读可写。想起我们第一次学习程序语言时,都喜欢用“Hello, World”
作为例子,这里,我们就把这个虚拟的设备命名为“hello”,而这个内核驱动程序也命名为hello 驱动程序。其实,Android 内
核驱动程序和一般Linux 内核驱动程序的编写方法是一样的,都是以Linux 模块的形式实现的,具体可参考前面Android 学习
启动篇一文中提到的Linux Device Drivers 一书。不过,这里我们还是从Android 系统的角度来描述Android 内核驱动程序的编
写和编译过程。
一. 参照这两篇文章在Ubuntu 上下载、编译和安装Android 最新源代码和在Ubuntu 上下载、编译和安装Android 最新内核源
代码(Linux Kernel)准备好Android 内核驱动程序开发环境。
二. 进入到kernel/common/drivers 目录,新建hello 目录:
linuxidc@www.linuxidc.com:~/Android$ cd kernel/common/drivers
linuxidc@www.linuxidc.com:~/Android/kernel/common/drivers$ mkdir hello
三. 在hello 目录中增加hello.h 文件:
1.#ifndef _HELLO_Android_H_
2.#define _HELLO_ANDROID_H_
3.#include <linux/cdev.h>
4.#include <linux/semaphore.h>
5.#define HELLO_DEVICE_NODE_NAME "hello"
8.#define HELLO_DEVICE_FILE_NAME "hello"
9.#define HELLO_DEVICE_PROC_NAME "hello"
10.#define HELLO_DEVICE_CLASS_NAME "hello"
12.struct hello_android_dev {
13. int val;
14. struct semaphore sem;
15. struct cdev dev;
16.};
17.#endif
这个头文件定义了一些字符串常量宏,在后面我们要用到。此外,还定义了一个字符设备结构体hello_Android_dev,这个就
是我们虚拟的硬件设备了,val 成员变量就代表设备里面的寄存器,它的类型为int,sem 成员变量是一个信号量,是用同步
访问寄存器val 的,dev 成员变量是一个内嵌的字符设备,这个Linux 驱动程序自定义字符设备结构体的标准方法。
四.在hello 目录中增加hello.c 文件,这是驱动程序的实现部分。驱动程序的功能主要是向上层提供访问设备的寄存器的值,
包括读和写。这里,提供了三种访问设备寄存器的方法,一是通过proc 文件系统来访问,二是通过传统的设备文件的方法来
访问,三是通过devfs 文件系统来访问。下面分段描述该驱动程序的实现。
首先是包含必要的头文件和定义三种访问设备的方法:
1.#include <linux/init.h>
2.#include <linux/module.h>
3.#include <linux/types.h>
4.#include <linux/fs.h>
5.#include <linux/proc_fs.h>
6.#include <linux/device.h>
7.#include <asm/uaccess.h>
8.
9.#include "hello.h"
10.
11./*主设备和从设备号变量*/
12.static int hello_major = 0;
13.static int hello_minor = 0;
14.
15./*设备类别和设备变量*/
16.static struct class* hello_class = NULL;
17.static struct hello_Android_dev* hello_dev = NULL;
18.
19./*传统的设备文件操作方法*/
20.static int hello_open(struct inode* inode, struct file* filp);
21.static int hello_release(struct inode* inode, struct file* filp);
22.static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
23.static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
24.
25./*设备文件操作方法表*/
26.static struct file_operations hello_fops = {
27. .owner = THIS_MODULE,
28. .open = hello_open,
29. .release = hello_release,
30. .read = hello_read,
31. .write = hello_write,
32.};
33.
34./*定义设备属性*/
35.static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);
36.
37./*访问设置属性方法*/
38.static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf);
39.static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count); 定义传统的设备文
件访问方法,主要是定义hello_open、hello_release、hello_read 和hello_write 这四个打开、释放、读和写设备文件的方法:
1./*打开设备方法*/
2.static int hello_open(struct inode* inode, struct file* filp) {
3. struct hello_Android_dev* dev;
4.
5. /*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
6. dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
7. filp->private_data = dev;
8.
9. return 0;
10.}
11.
12./*设备文件释放时调用,空实现*/
13.static int hello_release(struct inode* inode, struct file* filp) {
14. return 0;
15.}
16.
17./*读取设备的寄存器val 的值*/
18.static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
19. ssize_t err = 0;
20. struct hello_android_dev* dev = filp->private_data;
21.
22. /*同步访问*/
23. if(down_interruptible(&(dev->sem))) {
24. return -ERESTARTSYS;
25. }
26.
27. if(count < sizeof(dev->val)) {
28. goto out;
29. }
30.
31. /*将寄存器val 的值拷贝到用户提供的缓冲区*/
32. if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
33. err = -EFAULT;
34. goto out;
35. }
36.
37. err = sizeof(dev->val);
38.
39.out:
40. up(&(dev->sem));
41. return err;
42.}
43.
44./*写设备的寄存器值val*/
45.static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
46. struct hello_android_dev* dev = filp->private_data;
47. ssize_t err = 0;
48.
49. /*同步访问*/
50. if(down_interruptible(&(dev->sem))) {
51. return -ERESTARTSYS;
52. }
53.
54. if(count != sizeof(dev->val)) {
55. goto out;
56. }
57.
58. /*将用户提供的缓冲区的值写到设备寄存器去*/
59. if(copy_from_user(&(dev->val), buf, count)) {
60. err = -EFAULT;
61. goto out;
62. }
63.
64. err = sizeof(dev->val);
65.
66.out:
67. up(&(dev->sem));
68. return err;
69.}
定义通过devfs 文件系统访问方法,这里把设备的寄存器val 看成是设备的一个属性,通过读写这个属性来对设备进行访问,
主要是实现hello_val_show 和hello_val_store 两个方法,同时定义了两个内部使用的访问val 值的方法__hello_get_val 和
__hello_set_val:
1. /*读取寄存器val 的值到缓冲区buf 中,内部使用*/
2. static ssize_t __hello_get_val(struct hello_Android_dev* dev, char* buf) {
3. int val = 0;
4.
5. /*同步访问*/
6. if(down_interruptible(&(dev->sem))) {
7. return -ERESTARTSYS;
8. }
9.
10. val = dev->val;
11. up(&(dev->sem));
12.
13. return snprintf(buf, PAGE_SIZE, "%d/n", val);
14. }
15.
16. /*把缓冲区buf 的值写到设备寄存器val 中去,内部使用*/
17. static ssize_t __hello_set_val(struct hello_Android_dev* dev, const char* buf, size_t count) {
18. int val = 0;
19.
20. /*将字符串转换成数字*/
21. val = simple_strtol(buf, NULL, 10);
22.
23. /*同步访问*/
24. if(down_interruptible(&(dev->sem))) {
25. return -ERESTARTSYS;
26. }
27.
28. dev->val = val;
29. up(&(dev->sem));
30.
31. return count;
32. }
33.
34. /*读取设备属性val*/
35. static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
36. struct hello_Android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
37.
38. return __hello_get_val(hdev, buf);
39. }
40.
41. /*写设备属性val*/
42. static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t coun
t) {
43. struct hello_Android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
44.
45. return __hello_set_val(hdev, buf, count);
46. }
定义通过proc 文件系统访问方法,主要实现了hello_proc_read 和hello_proc_write 两个方法,同时定义了在proc 文件系统
创建和删除文件的方法hello_create_proc 和hello_remove_proc:
1. /*读取设备寄存器val 的值,保存在page 缓冲区中*/
2. static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {
3. if(off > 0) {
4. *eof = 1;
5. return 0;
6. }
7.
8. return __hello_get_val(hello_dev, page);
9. }
10.
11. /*把缓冲区的值buff 保存到设备寄存器val 中去*/
12. static ssize_t hello_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) {
13. int err = 0;
14. char* page = NULL;
15.
16. if(len > PAGE_SIZE) {
17. printk(KERN_ALERT"The buff is too large: %lu./n", len);
18. return -EFAULT;
19. }
20.
21. page = (char*)__get_free_page(GFP_KERNEL);
22. if(!page) {
23. printk(KERN_ALERT"Failed to alloc page./n");
24. return -ENOMEM;
25. }
26.
27. /*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/
28. if(copy_from_user(page, buff, len)) {
29. printk(KERN_ALERT"Failed to copy buff from user./n");
30. err = -EFAULT;
31. goto out;
32. }
33.
34. err = __hello_set_val(hello_dev, page, len);
35.
36. out:
37. free_page((unsigned long)page);
38. return err;
39. }
40.
41. /*创建/proc/hello 文件*/
42. static void hello_create_proc(void) {
43. struct proc_dir_entry* entry;
44.
45. entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
46. if(entry) {
47. entry->owner = THIS_MODULE;
48. entry->read_proc = hello_proc_read;
49. entry->write_proc = hello_proc_write;
50. }
51. }
52.
53. /*删除/proc/hello 文件*/
54. static void hello_remove_proc(void) {
55. remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
56. }
最后,定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作:
1./*初始化设备*/
2.static int __hello_setup_dev(struct hello_Android_dev* dev) {
3. int err;
4. dev_t devno = MKDEV(hello_major, hello_minor);
5.
6. memset(dev, 0, sizeof(struct hello_Android_dev));
7.
8. cdev_init(&(dev->dev), &hello_fops);
9. dev->dev.owner = THIS_MODULE;
10. dev->dev.ops = &hello_fops;
11.
12. /*注册字符设备*/
13. err = cdev_add(&(dev->dev),devno, 1);
14. if(err) {
15. return err;
16. }
17.
18. /*初始化信号量和寄存器val 的值*/
19. init_MUTEX(&(dev->sem));
20. dev->val = 0;
21.
22. return 0; 23.}
24.
25./*模块加载方法*/
26.static int __init hello_init(void){
27. int err = -1;
28. dev_t dev = 0;
29. struct device* temp = NULL;
30.
31. printk(KERN_ALERT"Initializing hello device./n");
32.
33. /*动态分配主设备和从设备号*/
34. err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
35. if(err < 0) {
36. printk(KERN_ALERT"Failed to alloc char dev region./n");
37. goto fail;
38. }
39.
40. hello_major = MAJOR(dev);
41. hello_minor = MINOR(dev);
42.
43. /*分配helo 设备结构体变量*/
44. hello_dev = kmalloc(sizeof(struct hello_Android_dev), GFP_KERNEL);
45. if(!hello_dev) {
46. err = -ENOMEM;
47. printk(KERN_ALERT"Failed to alloc hello_dev./n");
48. goto unregister;
49. }
50.
51. /*初始化设备*/
52. err = __hello_setup_dev(hello_dev);
53. if(err) {
54. printk(KERN_ALERT"Failed to setup dev: %d./n", err);
55. goto cleanup;
56. }
57.
58. /*在/sys/class/目录下创建设备类别目录hello*/
59. hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
60. if(IS_ERR(hello_class)) {
61. err = PTR_ERR(hello_class);
62. printk(KERN_ALERT"Failed to create hello class./n");
63. goto destroy_cdev;
64. }
65.
66. /*在/dev/目录和/sys/class/hello 目录下分别创建设备文件hello*/
67. temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
68. if(IS_ERR(temp)) {
69. err = PTR_ERR(temp);
70. printk(KERN_ALERT"Failed to create hello device.");
71. goto destroy_class;
72. }
73.
74. /*在/sys/class/hello/hello 目录下创建属性文件val*/
75. err = device_create_file(temp, &dev_attr_val);
76. if(err < 0) {
77. printk(KERN_ALERT"Failed to create attribute val.");
78. goto destroy_device;
79. }
80.
81. dev_set_drvdata(temp, hello_dev);
82.
83. /*创建/proc/hello 文件*/
84. hello_create_proc();
85.
86. printk(KERN_ALERT"Succedded to initialize hello device./n");
87. return 0;
88.
89.destroy_device:
90. device_destroy(hello_class, dev);
91.
92.destroy_class:
93. class_destroy(hello_class);
94.
95.destroy_cdev:
96. cdev_del(&(hello_dev->dev));
97.
98.cleanup:
99. kfree(hello_dev);
100.
101.unregister:
102. unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);
103.
104.fail:
105. return err;
106.}
107.
108./*模块卸载方法*/
109.static void __exit hello_exit(void) {
110. dev_t devno = MKDEV(hello_major, hello_minor);
111.
112. printk(KERN_ALERT"Destroy hello device./n");
113.
114. /*删除/proc/hello 文件*/
115. hello_remove_proc();
116.
117. /*销毁设备类别和设备*/
118. if(hello_class) {
119. device_destroy(hello_class, MKDEV(hello_major, hello_minor));
120. class_destroy(hello_class);
121. }
122.
123. /*删除字符设备和释放设备内存*/
124. if(hello_dev) {
125. cdev_del(&(hello_dev->dev));
126. kfree(hello_dev);
127. }
128.
129. /*释放设备号*/
130. unregister_chrdev_region(devno, 1);
131.}
132.
133.MODULE_LICENSE("GPL");
134.MODULE_DESCRIPTION("First Android Driver");
135.
136.module_init(hello_init);
137.module_exit(hello_exit);
五.在hello 目录中新增Kconfig 和Makefile 两个文件,其中Kconfig 是在编译前执行配置命令make menuconfig 时用到的,而
Makefile 是执行编译命令make 是用到的:
Kconfig 文件的内容
config HELLO
tristate "First Android Driver"
default n
help
This is the first Android driver.
Makefile 文件的内容
obj-$(CONFIG_HELLO) += hello.o
在Kconfig 文件中,tristate 表示编译选项HELLO 支持在编译内核时,hello 模块支持以模块、内建和不编译三种编译方
法,默认是不编译,因此,在编译内核前,我们还需要执行make menuconfig 命令来配置编译选项,使得hello 可以以模块或
者内建的方法进行编译。
在Makefile 文件中,根据选项HELLO 的值,执行不同的编译方法。
六. 修改arch/arm/Kconfig 和drivers/kconfig 两个文件,在menu "Device Drivers"和endmenu 之间添加一行:
source "drivers/hello/Kconfig"
这样,执行make menuconfig 时,就可以配置hello 模块的编译选项了。.
七. 修改drivers/Makefile 文件,添加一行:
obj-$(CONFIG_HELLO) += hello/
八. 配置编译选项:
linuxidc@www.linuxdc.com:~/Android/kernel/common$ make menuconfig
找到"Device Drivers" => "First Android Drivers"选项,设置为y。
注意,如果内核不支持动态加载模块,这里不能选择m,虽然我们在Kconfig 文件中配置了HELLO 选项为tristate。
要支持动态加载模块选项,必须要在配置菜单中选择Enable loadable module support 选项;在支持动态卸载模块选项,必须
要在Enable loadable module support 菜单项中,选择Module unloading 选项。
九. 编译:
linuxidc@www.linuxdc.com:~/Android/kernel/common$ make
编译成功后,就可以在hello 目录下看到hello.o 文件了,这时候编译出来的zImage 已经包含了hello 驱动。
十. 参照在Ubuntu 上下载、编译和安装Android 最新内核源代码(Linux Kernel)一文所示,运行新编译的内核文件,
验证hello 驱动程序是否已经正常安装:
linuxidc@www.linuxdc.com:~/Android$ emulator -kernel ./kernel/common/arch/arm/boot/zImage &
linuxidc@www.linuxdc.com:~/Android$ adb shell
进入到dev 目录,可以看到hello 设备文件:
root@Android:/ # cd dev
root@Android:/dev # ls
进入到proc 目录,可以看到hello 文件:
root@Android:/ # cd proc
root@Android:/proc # ls
访问hello 文件的值:
root@Android:/proc # cat hello
0
root@Android:/proc # echo '5' > hello
root@Android:/proc # cat hello
5
进入到sys/class 目录,可以看到hello 目录:
root@Android:/ # cd sys/class
root@Android:/sys/class # ls
进入到hello 目录,可以看到hello 目录:
root@Android:/sys/class # cd hello
root@Android:/sys/class/hello # ls
进入到下一层hello 目录,可以看到val 文件:
root@Android:/sys/class/hello # cd hello
root@Android:/sys/class/hello/hello # ls
访问属性文件val 的值:
root@Android:/sys/class/hello/hello # cat val
5
root@Android:/sys/class/hello/hello # echo '0' > val
root@Android:/sys/class/hello/hello # cat val
0
至此,我们的hello 内核驱动程序就完成了,并且验证一切正常。这里我们采用的是系统提供的方法和驱动程序进行
交互,也就是通过proc 文件系统和devfs 文件系统的方法,下一篇文章中,我们将通过自己编译的C 语言程序来访问/dev/hello
文件来和hello 驱动程序交互,敬请期待。
在Android 系统中增加C 可执行程序来访问硬件驱动程序。
在前一篇文章http://www.linuxidc.com/Linux/2011-07/38977.htm 中,我们介绍了如何在Ubuntu 上为Android 系统编写Linux
内核驱动程序。在这个名为hello 的Linux 内核驱动程序中,创建三个不同的文件节点来供用户空间访问,分别是传统的设备
文件/dev/hello、proc 系统文件/proc/hello 和devfs 系统属性文件/sys/class/hello/hello/val。进一步,还通过cat 命令来直接访
问/proc/hello 和/sys/class/hello/hello/val 文件来,以验证驱动程序的正确性。在这一篇文章里,我们将通过自己编写的C 可执
行程序来访问设备文件/dev/hello。可能读者会觉得奇怪,怎么能在Android 系统中用C 语言来编写应用程序呢?Android 系统
上的应用程序不都是Java 应用程序吗?其实是可以的,读者不妨用adb shell 命令连上Android 模拟器,在/system/bin 目录下
可以看到很多C 可执行程序,如cat 命令。今天,我们就来学习一下怎么在Android 系统中添加用C 语言编写的可执行程序吧。
一. 参照在Ubuntu 上为Android 系统编写Linux 内核驱动程序一文,准备好Linux 驱动程序。使用Android 模拟器加载
包含这个Linux 驱动程序的内核文件,并且使用adb shell 命令连接上模拟,验证在/dev 目录中存在设备文件hello。
二. 进入到Android 源代码工程的external 目录,创建hello 目录:
linuxidc@www.linuxidc.com:~/Android$ cd external
linuxidc@www.linuxidc.com:~/Android/external$ mkdir hello
三. 在hello 目录中新建hello.c 文件:
1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <fcntl.h>
4.#define DEVICE_NAME "/dev/hello"
5.int main(int argc, char** argv)
6.{
7. int fd = -1;
8. int val = 0;
9. fd = open(DEVICE_NAME, O_RDWR);
10. if(fd == -1) {
11. printf("Failed to open device %s./n", DEVICE_NAME);
12. return -1;
13. }
14.
15. printf("Read original value:/n");
16. read(fd, &val, sizeof(val));
17. printf("%d./n/n", val);
18. val = 5;
19. printf("Write value %d to %s./n/n", val, DEVICE_NAME);
20. write(fd, &val, sizeof(val));
21.
22. printf("Read the value again:/n");
23. read(fd, &val, sizeof(val));
24. printf("%d./n/n", val);
25. close(fd);
26. return 0;
27.}
这个程序的作用中,打开/dev/hello 文件,然后先读出/dev/hello 文件中的值,接着写入值5 到/dev/hello 中去,最后再
次读出/dev/hello 文件中的值,看看是否是我们刚才写入的值5。从/dev/hello 文件读写的值实际上就是我们虚拟的硬件的寄
存器val 的值。
四. 在hello 目录中新建Android.mk 文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := hello
LOCAL_SRC_FILES := $(call all-subdir-c-files)
include $(BUILD_EXECUTABLE)
注意,BUILD_EXECUTABLE 表示我们要编译的是可执行程序。
五. 参照如何单独编译Android 源代码中的模块一文,使用mmm 命令进行编译:
linuxidc@www.linuxidc.com:~/Android$ mmm ./external/hello
编译成功后,就可以在out/target/product/gerneric/system/bin 目录下,看到可执行文件hello 了。
六. 重新打包Android 系统文件system.img:
linuxidc@www.linuxidc.com:~/Android$ make snod
这样,重新打包后的system.img 文件就包含刚才编译好的hello 可执行文件了。
七. 运行模拟器,使用/system/bin/hello 可执行程序来访问Linux 内核驱动程序:
linuxidc@www.linuxidc.com:~/Android$ emulator -kernel ./kernel/common/arch/arm/boot/zImage &
linuxidc@www.linuxidc.com:~/Android$ adb shell
root@Android:/ # cd system/bin
root@Android:/system/bin # ./hello
Read the original value:
0.
Write value 5 to /dev/hello.
Read the value again:
5.
看到这个结果,就说我们编写的C 可执行程序可以访问我们编写的Linux 内核驱动程序了。
介绍完了如何使用C 语言编写的可执行程序来访问我们的Linux 内核驱动程序,读者可能会问,能不能在Android 的
Application Frameworks 提供Java 接口来访问Linux 内核驱动程序呢?可以的,接下来的几篇文章中,我们将介绍如何在Android
的Application Frameworks 中,增加Java 接口来访问Linux 内核驱动程序,敬请期待。
在Ubuntu 上为Android 增加硬件抽象层(HAL)模块访问Linux 内核驱动程序
在Android 硬件抽象层(HAL)概要介绍和学习计划一文中,我们简要介绍了在Android 系统为为硬件编写驱动程序的方法。
简单来说,硬件驱动程序一方面分布在Linux 内核中,另一方面分布在用户空间的硬件抽象层中。接着,在Ubuntu 上为Android
系统编写Linux 内核驱动程序
一文中举例子说明了如何在Linux 内核编写驱动程序。在这一篇文章中,我们将继续介绍Android 系统硬件驱动程序的另一
方面实现,即如何在硬件抽象层中增加硬件模块来和内核驱动程序交互。在这篇文章中,我们还将学习到如何在Android 系
统创建设备文件时用类似Linux 的udev 规则修改设备文件模式的方法。
一. 参照在Ubuntu 上为Android 系统编写Linux 内核驱动程序一文所示,准备好示例内核驱动序。完成这个内核驱动程序后,
便可以在Android 系统中得到三个文件,分别是/dev/hello、/sys/class/hello/hello/val 和/proc/hello。在本文中,我们将通过设
备文件/dev/hello 来连接硬件抽象层模块和Linux 内核驱动程序模块。
二. 进入到在hardware/libhardware/include/hardware 目录,新建hello.h 文件:
linuxidc@www.linuxidc.com:~/Android$ cd hardware/libhardware/include/hardware
linuxidc@www.linuxidc.com:~/Android/hardware/libhardware/include/hardware$ vi hello.h
hello.h 文件的内容如下:
1.#ifndef Android_HELLO_INTERFACE_H
2.#define ANDROID_HELLO_INTERFACE_H
3.#include <hardware/hardware.h>
4.
5.__BEGIN_DECLS
6.
7./*定义模块ID*/
8.#define HELLO_HARDWARE_MODULE_ID "hello"
9.
10./*硬件模块结构体*/
11.struct hello_module_t {
12. struct hw_module_t common;
13.};
14.
15./*硬件接口结构体*/
16.struct hello_device_t {
17. struct hw_device_t common;
18. int fd;
19. int (*set_val)(struct hello_device_t* dev, int val);
20. int (*get_val)(struct hello_device_t* dev, int* val);
21.};
22.
23.__END_DECLS
24.
25.#endif 这里按照Android 硬件抽象层规范的要求,分别定义模块ID、模块结构体以及硬件接口结构体。在硬件接口结构
体中,fd 表示设备文件描述符,对应我们将要处理的设备文件"/dev/hello",set_val 和get_val 为该HAL 对上提供的函数接口。
三. 进入到hardware/libhardware/modules 目录,新建hello 目录,并添加hello.c 文件。 hello.c 的内容较多,我们分段来看。
首先是包含相关头文件和定义相关结构:
1.#define LOG_TAG "HelloStub"
2.
3.#include <hardware/hardware.h>
4.#include <hardware/hello.h>
5.#include <fcntl.h>
6.#include <errno.h>
7.#include <cutils/log.h>
8.#include <cutils/atomic.h>
9.
10.#define DEVICE_NAME "/dev/hello"
11.#define MODULE_NAME "Hello"
12.#define MODULE_AUTHOR "shyluo@gmail.com"
13.
14./*设备打开和关闭接口*/
15.static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);
16.static int hello_device_close(struct hw_device_t* device);
17.
18./*设备访问接口*/
19.static int hello_set_val(struct hello_device_t* dev, int val);
20.static int hello_get_val(struct hello_device_t* dev, int* val);
21.
22./*模块方法表*/
23.static struct hw_module_methods_t hello_module_methods = {
24. open: hello_device_open
25.};
26.
27./*模块实例变量*/
28.struct hello_module_t HAL_MODULE_INFO_SYM = {
29. common: {
30. tag: HARDWARE_MODULE_TAG,
31. version_major: 1,
32. version_minor: 0,
33. id: HELLO_HARDWARE_MODULE_ID,
34. name: MODULE_NAME,
35. author: MODULE_AUTHOR,
36. methods: &hello_module_methods,
37. }
38.};
这里,实例变量名必须为HAL_MODULE_INFO_SYM,tag 也必须为HARDWARE_MODULE_TAG,这是Android 硬件抽象层规范规
定的。
定义hello_device_open 函数:
1.static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) {
2. struct hello_device_t* dev;dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t));
2.
3. if(!dev) {
4. LOGE("Hello Stub: failed to alloc space");
5. return -EFAULT;
6. }
7.
8. memset(dev, 0, sizeof(struct hello_device_t));
9. dev->common.tag = HARDWARE_DEVICE_TAG;
10. dev->common.version = 0;
11. dev->common.module = (hw_module_t*)module;
12. dev->common.close = hello_device_close;
13. dev->set_val = hello_set_val;dev->get_val = hello_get_val;
14.
15. if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
16. LOGE("Hello Stub: failed to open /dev/hello -- %s.", strerror(errno));free(dev);
17. return -EFAULT; 18. }
19.
20. *device = &(dev->common);
21. LOGI("Hello Stub: open /dev/hello successfully.");
22.
23. return 0;
24.}
DEVICE_NAME 定义为"/dev/hello"。由于设备文件是在内核驱动里面通过device_create 创建的,而device_create 创建的设备文
件默认只有root 用户可读写,而hello_device_open 一般是由上层APP 来调用的,这些APP 一般不具有root 权限,这时候就
导致打开设备文件失败:
Hello Stub: failed to open /dev/hello -- Permission denied.
解决办法是类似于Linux 的udev 规则,打开Android 源代码工程目录下,进入到system/core/rootdir 目录,里面有一个名为
uevent.rc 文件,往里面添加一行:
/dev/hello 0666 root root
定义hello_device_close、hello_set_val 和hello_get_val 这三个函数:
1.static int hello_device_close(struct hw_device_t* device) {
2. struct hello_device_t* hello_device = (struct hello_device_t*)device;
3.
4. if(hello_device) {
5. close(hello_device->fd);
6. free(hello_device);
7. }
8.
9. return 0;
10.}
11.
12.static int hello_set_val(struct hello_device_t* dev, int val) {
13. LOGI("Hello Stub: set value %d to device.", val);
14.
15. write(dev->fd, &val, sizeof(val));
16.
17. return 0;
18.}
19.
20.static int hello_get_val(struct hello_device_t* dev, int* val) {
21. if(!val) {
22. LOGE("Hello Stub: error val pointer");
23. return -EFAULT;
24. }
25.
26. read(dev->fd, val, sizeof(*val));
27.
28. LOGI("Hello Stub: get value %d from device", *val);
29.
30. return 0;
31.}
四. 继续在hello 目录下新建Android.mk 文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hello.c
LOCAL_MODULE := hello.default
include $(BUILD_SHARED_LIBRARY)
注意,LOCAL_MODULE 的定义规则,hello 后面跟有default,hello.default 能够保证我们的模块总能被硬象抽象层加载到。
五. 编译:
USER-NAME@MACHINE-NAME:~/Android$ mmm hardware/libhardware/moudles/hello
编译成功后,就可以在out/target/product/generic/system/lib/hw 目录下看到hello.default.so 文件了。
六. 重新打包Android 系统镜像system.img:
USER-NAME@MACHINE-NAME:~/Android$ make snod
重新打包后,system.img 就包含我们定义的硬件抽象层模块hello.default 了。
虽然我们在Android 系统为我们自己的硬件增加了一个硬件抽象层模块,但是现在Java 应用程序还不能访问到我们的
硬件。我们还必须编写JNI 方法和在Android 的Application Frameworks 层增加API 接口,才能让上层Application 访问我们的
硬件。在接下来的文章中,我们还将完成这一系统过程,使得我们能够在Java 应用程序中访问我们自己定制的硬件。
为Android 硬件抽象层(HAL)模块编写JNI 方法提供Java 访问硬件服务接口
在上两篇文章中(http://www.linuxidc.com/Linux/2011-07/38978.htm 与 http://www.linuxidc.com/Linux/2011-07/38980.htm),我
们介绍了如何为Android 系统的硬件编写驱动程序,包括如何在Linux 内核空间实现内核驱动程序和在用户空间实现硬件抽象
层接口。实现这两者的目的是为了向更上一层提供硬件访问接口,即为Android 的Application Frameworks 层提供硬件服务。
我们知道,Android 系统的应用程序是用Java 语言编写的,而硬件驱动程序是用C 语言来实现的,那么,Java 接口如何去访
问C 接口呢?众所周知,Java 提供了JNI 方法调用,同样,在Android 系统中,Java 应用程序通过JNI 来调用硬件抽象层接口。
在这一篇文章中,我们将介绍如何为Android 硬件抽象层接口编写JNI 方法,以便使得上层的Java 应用程序能够使用下层提
供的硬件服务。
一. 参照在Ubuntu 上为Android 增加硬件抽象层(HAL)模块访问Linux 内核驱动程序一文,准备好硬件抽象层模块,确保
Android 系统镜像文件system.img 已经包含hello.default 模块。
二. 进入到frameworks/base/services/jni 目录,新建com_Android_server_HelloService.cpp 文件:
linuxidc@www.linuxidc.com:~/Android$ cd frameworks/base/services/jni
linuxidc@www.linuxidc.com:~/Android/frameworks/base/services/jni$ vi com_android_server_HelloService.cpp
在com_Android_server_HelloService.cpp 文件中,实现JNI 方法。注意文件的命令方法,com_android_server 前缀表示的是包
名,表示硬件服务HelloService 是放在frameworks/base/services/java 目录下的com/android/server 目录的,即存在一个命令为
com.android.server.HelloService 的类。这里,我们暂时略去HelloService 类的描述,在下一篇文章中,我们将回到HelloService
类来。简单地说,HelloService 是一个提供Java 接口的硬件访问服务类。
首先是包含相应的头文件:
1.#define LOG_TAG "HelloService"
2.#include "jni.h"
3.#include "JNIHelp.h"
4.#include "Android_runtime/AndroidRuntime.h"
5.#include <utils/misc.h>
6.#include <utils/Log.h>
7.#include <hardware/hardware.h>
8.#include <hardware/hello.h>
9.#include <stdio.h> 接着定义hello_init、hello_getVal 和hello_setVal 三个JNI 方法:
1.namespace Android
2.{
3. /*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hello.h>*/
4. struct hello_device_t* hello_device = NULL;
5. /*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val 的值*/
6. static void hello_setVal(JNIEnv* env, jobject clazz, jint value) {
7. int val = value;
8. LOGI("Hello JNI: set value %d to device.", val);
9. if(!hello_device) {
10. LOGI("Hello JNI: device is not open.");
11. return;
12. }
13.
14. hello_device->set_val(hello_device, val);
15. }
16. /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val 的值*/
17. static jint hello_getVal(JNIEnv* env, jobject clazz) {
18. int val = 0;
19. if(!hello_device) {
20. LOGI("Hello JNI: device is not open.");
21. return val;
22. }
23. hello_device->get_val(hello_device, &val);
24.
25. LOGI("Hello JNI: get value %d from device.", val);
26.
27. return val;
28. }
29. /*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
30. static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {
31. return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
32. }
33. /*通过硬件模块ID 来加载指定的硬件抽象层模块并打开硬件*/
34. static jboolean hello_init(JNIEnv* env, jclass clazz) {
35. hello_module_t* module;
36.
37. LOGI("Hello JNI: initializing......");
38. if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {
39. LOGI("Hello JNI: hello Stub found.");
40. if(hello_device_open(&(module->common), &hello_device) == 0) {
41. LOGI("Hello JNI: hello device is open.");
42. return 0;
43. }
44. LOGE("Hello JNI: failed to open hello device.");
45. return -1;
46. }
47. LOGE("Hello JNI: failed to get hello stub module.");
48. return -1;
49. }
50. /*JNI 方法表*/
51. static const JNINativeMethod method_table[] = {
52. {"init_native", "()Z", (void*)hello_init},
53. {"setVal_native", "(I)V", (void*)hello_setVal},
54. {"getVal_native", "()I", (void*)hello_getVal},
55. };
56. /*注册JNI 方法*/
57. int register_Android_server_HelloService(JNIEnv *env) {
58. return jniRegisterNativeMethods(env, "com/Android/server/HelloService", method_table, NELEM(method_table));
59. }
60.};
注意,在hello_init函数中,通过Android 硬件抽象层提供的hw_get_module 方法来加载模块ID为HELLO_HARDWARE_MODULE_ID
的硬件抽象层模块,其中,HELLO_HARDWARE_MODULE_ID 是在<hardware/hello.h>中定义的。Android 硬件抽象层会根据
HELLO_HARDWARE_MODULE_ID 的值在Android 系统的/system/lib/hw 目录中找到相应的模块,然后加载起来,并且返回
hw_module_t 接口给调用者使用。在jniRegisterNativeMethods 函数中,第二个参数的值必须对应HelloService 所在的包的路径,
即com.android.server.HelloService。
三. 修改同目录下的onload.cpp 文件,首先在namespace Android 增加register_android_server_HelloService 函数声明:
namespace Android {
int register_Android_server_HelloService(JNIEnv *env);
};
在JNI_onLoad 增加register_Android_server_HelloService 函数调用:
extern "C" jint JNI_onLoad(JavaVM* vm, void* reserved)
{
register_android_server_HelloService(JNIEnv *env);
}
这样,在Android 系统初始化时,就会自动加载该JNI 方法调用表。
四. 修改同目录下的Android.mk 文件,在LOCAL_SRC_FILES 变量中增加一行:
LOCAL_SRC_FILES:= /
com_android_server_AlarmManagerService.cpp /
com_android_server_BatteryService.cpp /
com_android_server_InputManager.cpp /
com_android_server_LightsService.cpp /
com_android_server_PowerManagerService.cpp /
com_android_server_SystemServer.cpp /
com_android_server_UsbService.cpp /
com_android_server_VibratorService.cpp /
com_android_server_location_GpsLocationProvider.cpp /
com_android_server_HelloService.cpp /
onload.cpp
五. 编译和重新找亿system.img:
USER-NAME@MACHINE-NAME:~/Android$ mmm frameworks/base/services/jni
USER-NAME@MACHINE-NAME:~/Android$ make snod
这样,重新打包的system.img 镜像文件就包含我们刚才编写的JNI 方法了,也就是我们可以通过Android 系统的
Application Frameworks 层提供的硬件服务HelloService 来调用这些JNI 方法,进而调用低层的硬件抽象层接口去访问硬件了。
前面提到, 在这篇文章中, 我们暂时忽略了HelloService 类的实现, 在下一篇文章中
http://www.linuxidc.com/Linux/2011-07/38982.htm,我们将描述如何实现硬件服务HelloService。
在Ubuntu 上为Android 系统的Application Frameworks 层增加硬件访问服务
在数字科技日新月异的今天,软件和硬件的完美结合,造就了智能移动设备的流行。今天大家对iOS 和Android 系统的趋之若
鹜,一定程度上是由于这两个系统上有着丰富多彩的各种应用软件。因此,软件和硬件的关系,在一定程度上可以说,硬件
是为软件服务的。硬件工程师研发出一款硬件设备,自然少了软件工程师为其编写驱动程序;而驱动程序的最终目的,是为
了使得最上层的应用程序能够使用这些硬件提供的服务来为用户提供软件功能。对Android 系统上的应用软件来说,就是要
在系统的Application Frameworks 层为其提供硬件服务。在前面的几篇文章中,我们着重介绍了Linux 内核层、硬件抽象层和
运行时库层提供的自定义硬件服务接口,这些接口都是通过C 或者C++语言来实现的。在这一篇文章中,我们将介绍如何在
Android 系统的Application Frameworks 层提供Java 接口的硬件服务。
一. 参照在Ubuntu 为Android 硬件抽象层(HAL)模块编写JNI 方法提供Java 访问硬件服务接口一文所示,为硬件抽象层模块
准备好JNI 方法调用层。
二. 在Android 系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序提供服务。因此,调用这些硬件服务的应用
程序与这些硬件服务之间的通信需要通过代理来进行。为此, 我们要先定义好通信接口。进入到
frameworks/base/core/java/android/os 目录,新增IHelloService.aidl 接口定义文件:
linuxidc@www.linuxidc.com:~/Android$ cd frameworks/base/core/java/android/os
linuxidc@www.linuxidc.com:~/Android/frameworks/base/core/java/android/os$ vi IHelloService.aidl
IHelloService.aidl 定义了IHelloService 接口:
1.package Android.os;
2.
3.interface IHelloService {
4. void setVal(int val);
5. int getVal(); 6.}
IHelloService 接口主要提供了设备和获取硬件寄存器val 的值的功能,分别通过setVal 和getVal 两个函数来实现。
三.返回到frameworks/base 目录,打开Android.mk 文件,修改LOCAL_SRC_FILES 变量的值,增加IHelloService.aidl 源文件:
## READ ME: ########################################################
##
## When updating this list of aidl files, consider if that aidl is
## part of the SDK API. If it is, also add it to the list below that
## is preprocessed and distributed with the SDK. This list should
## not contain any aidl files for parcelables, but the one below should
## if you intend for 3rd parties to be able to send those objects
## across process boundaries.
##
## READ ME: ########################################################
LOCAL_SRC_FILES += /
core/java/Android/os/IVibratorService.aidl /
core/java/Android/os/IHelloService.aidl /
core/java/Android/service/urlrenderer/IUrlRendererService.aidl /
四. 编译IHelloService.aidl 接口:
linuxidc@www.linuxidc.com:~/Android$ mmm frameworks/base
这样,就会根据IHelloService.aidl 生成相应的IHelloService.Stub 接口。
五.进入到frameworks/base/services/java/com/Android/server 目录,新增HelloService.java 文件:
1.package com.Android.server;
2.import Android.content.Context;
2.import Android.os.IHelloService;
3.import Android.util.Slog;
4.public class HelloService extends IHelloService.Stub {
5. private static final String TAG = "HelloService";
6. HelloService() {
7. init_native();
8. }
9. public void setVal(int val) {
10. setVal_native(val);
11. }
12. public int getVal() {
13. return getVal_native();
14. }
15.
16. private static native boolean init_native();
17. private static native void setVal_native(int val);
18. private static native int getVal_native();
19.};
HelloService 主要是通过调用JNI 方法init_native、setVal_native 和getVal_native(见在Ubuntu 为Android 硬件抽象层(HAL)模
块编写JNI 方法提供Java 访问硬件服务接口一文)来提供硬件服务。
六. 修改同目录的SystemServer.java 文件,在ServerThread::run 函数中增加加载HelloService 的代码:
@Override
public void run() {
try {
Slog.i(TAG, "DiskStats Service");
ServiceManager.addService("diskstats", new DiskStatsService(context));
} catch (Throwable e) {
Slog.e(TAG, "Failure starting DiskStats Service", e);
}
try {
Slog.i(TAG, "Hello Service");
ServiceManager.addService("hello", new HelloService());
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Hello Service", e);
}
}
七. 编译HelloService 和重新打包system.img:
linuxidc@www.linuxidc.com:~/Android$ mmm frameworks/base/services/java
linuxidc@www.linuxidc.com:~/Android$ make snod
这样,重新打包后的system.img 系统镜像文件就在Application Frameworks 层中包含了我们自定义的硬件服务HelloService
了,并且会在系统启动的时候,自动加载HelloService。这时,应用程序就可以通过Java 接口来访问Hello 硬件服务了。我们
将 在下一篇文章http://www.linuxidc.com/Linux/2011-07/38983.htm中描述如何编写一个Java 应用程序来调用这个HelloService
接口来访问硬件。
为Android 系统内置Java 应用程序测试Application Frameworks 层的硬件服务
我们在Android 系统增加硬件服务的目的是为了让应用层的APP 能够通过Java 接口来访问硬件服务。那么, APP 如何通过Java
接口来访问Application Frameworks 层提供的硬件服务呢?在这一篇文章中,我们将在Android 系统的应用层增加一个内置的
应用程序,这个内置的应用程序通过ServiceManager 接口获取指定的服务,然后通过这个服务来获得硬件服务。
一. 参照在Ubuntu 上为Android 系统的Application Frameworks 层增加硬件访问服务
一文,在Application Frameworks 层定义好自己的硬件服务HelloService,并提供IHelloService 接口提供访问服务。
二. 为了方便开发,我们可以在IDE 环境下使用Android SDK 来开发Android 应用程序。开发完成后,再把程序源代码移植到
Android 源代码工程目录中。使用Eclipse 的Android 插件ADT 创建Android 工程很方便,这里不述,可以参考网上其它资料。
工程名称为Hello,下面主例出主要文件:
主程序是src/shy/luo/hello/Hello.java:
1.package shy.luo.hello;
2.
3.import shy.luo.hello.R;
4.import Android.app.Activity;
5.import Android.os.ServiceManager;
6.import Android.os.Bundle;
7.import Android.os.IHelloService;
8.import Android.os.RemoteException;
9.import Android.util.Log;
10.import Android.view.View;
11.import Android.view.View.OnClickListener;
12.import Android.widget.Button;
13.import Android.widget.EditText;
14.
15.public class Hello extends Activity implements OnClickListener {
16. private final static String LOG_TAG = "shy.luo.renju.Hello";
17.
18. private IHelloService helloService = null;
19.
20. private EditText valueText = null;
21. private Button readButton = null;
22. private Button writeButton = null;
23. private Button clearButton = null;
24.
25. /** Called when the activity is first created. */
26. @Override
27. public void onCreate(Bundle savedInstanceState) {
28. super.onCreate(savedInstanceState);
29. setContentView(R.layout.main);
30.
31. helloService = IHelloService.Stub.asInterface(
32. ServiceManager.getService("hello"));
33.
34. valueText = (EditText)findViewById(R.id.edit_value);
35. readButton = (Button)findViewById(R.id.button_read);
36. writeButton = (Button)findViewById(R.id.button_write);
37. clearButton = (Button)findViewById(R.id.button_clear);
38.
39. readButton.setOnClickListener(this);
40. writeButton.setOnClickListener(this);
41. clearButton.setOnClickListener(this);
42.
43. Log.i(LOG_TAG, "Hello Activity Created");
44. }
45.
46. @Override
47. public void onClick(View v) {
48. if(v.equals(readButton)) {
49. try {
50. int val = helloService.getVal();
51. String text = String.valueOf(val);
52. valueText.setText(text);
53. } catch (RemoteException e) {
54. Log.e(LOG_TAG, "Remote Exception while reading value from device.");
55. }
56. }
57. else if(v.equals(writeButton)) {
58. try {
59. String text = valueText.getText().toString();
60. int val = Integer.parseInt(text);
61. helloService.setVal(val);
62. } catch (RemoteException e) {
63. Log.e(LOG_TAG, "Remote Exception while writing value to device.");
64. }
65. }
66. else if(v.equals(clearButton)) {
67. String text = "";
68. valueText.setText(text);
69. }
70. }
71.}
程序通过ServiceManager.getService("hello")来获得HelloService,接着通过IHelloService.Stub.asInterface 函数转换为IHelloService
接口。其中,服务名字“hello”是系统启动时加载HelloService 时指定的,而IHelloService 接口定义在Android.os.IHelloService
中,具体可以参考在Ubuntu 上为Android 系统的Application Frameworks 层增加硬件访问服务一文。这个程序提供了简单的读
定自定义硬件有寄存器val 的值的功能,通过IHelloService.getVal 和IHelloService.setVal 两个接口实现。
界面布局文件res/layout/main.xml:
1.<?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:Android="http://schemas.android.com/apk/res/android"
3. Android:orientation="vertical"
4. Android:layout_width="fill_parent"
5. Android:layout_height="fill_parent">
6. <LinearLayout
7. Android:layout_width="fill_parent"
8. Android:layout_height="wrap_content"
9. Android:orientation="vertical"
10. Android:gravity="center">
11. <TextView
12. Android:layout_width="wrap_content"
13. Android:layout_height="wrap_content"
14. Android:text="@string/value">
15. </TextView>
16. <EditText
17. Android:layout_width="fill_parent"
18. Android:layout_height="wrap_content"
19. Android:id="@+id/edit_value"
20. Android:hint="@string/hint">
21. </EditText>
22. </LinearLayout>
23. <LinearLayout
24. Android:layout_width="fill_parent"
25. Android:layout_height="wrap_content"
26. Android:orientation="horizontal"
27. Android:gravity="center">
28. <Button
29. Android:id="@+id/button_read"
30. Android:layout_width="wrap_content"
31. Android:layout_height="wrap_content"
32. Android:text="@string/read">
33. </Button>
34. <Button
35. Android:id="@+id/button_write"
36. Android:layout_width="wrap_content"
37. Android:layout_height="wrap_content"
38. Android:text="@string/write">
39. </Button>
40. <Button
41. Android:id="@+id/button_clear"
42. Android:layout_width="wrap_content"
43. Android:layout_height="wrap_content"
44. Android:text="@string/clear">
45. </Button>
46. </LinearLayout>
47. </LinearLayout> 字符串文件res/values/strings.xml:
1.<?xml version="1.0" encoding="utf-8"?>
2. <resources>
3. <string name="app_name">Hello</string>
4. <string name="value">Value</string>
5. <string name="hint">Please input a value...</string>
6. <string name="read">Read</string>
7. <string name="write">Write</string>
8. <string name="clear">Clear</string>
9. </resources> 程序描述文件AndroidManifest.xml:
1.<?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:Android="http://schemas.android.com/apk/res/android"
3. package="shy.luo.hello"
4. Android:versionCode="1"
5. Android:versionName="1.0">
6. <application Android:icon="@drawable/icon" android:label="@string/app_name">
7. <activity Android:name=".Hello"
8. Android:label="@string/app_name">
9. <intent-filter>
10. <action Android:name="android.intent.action.MAIN" />
11. <category Android:name="android.intent.category.LAUNCHER" />
12. </intent-filter>
13. </activity>
14. </application>
15. <uses-sdk
16. Android:minSdkVersion="7"
17. Android:targetSdkVersion="7">
18. </uses-sdk>
19. </manifest>
三. 将Hello 目录拷贝至packages/experimental 目录,新增Android.mk 文件:
linuxidc@www.linuxidc.com:~/Android/packages/experimental$ vi Android.mkAndroid.mk 的文件内容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := Hello
include $(BUILD_PACKAGE)
四. 编译:
linuxidc@www.linuxidc.com:~/Android$ mmm packages/experimental/Hello
编译成功后,便可以在out/target/product/generic/system/app 目录下看到Hello.apk 文件了。
五. 重新打包系统镜像文件system.img:
linuxidc@www.linuxidc.com:~/Android$ make snod
重新打包后的system.img 文件就内置了Hello.apk 文件了。
六. 运行Android 模拟器:
linuxidc@www.linuxidc.com:~/Android$ emulator -kernel kernel/common/arch/arm/boot/zImage &
在Home Screen 中可以看到Hello 应用程序:
打开Hello 应用程序:
点击Read 按钮,可以从HelloService 中读取硬件寄存器val 的值;点击Clear 按钮,可以清空文本框的值;在文本框中输入一
个数值,再点击Write 按钮,便可以将这个值写入到硬件寄存器val 中去,可以再次点击Read 按钮来验证是否正确写入了值。
至此,我们就完整地学习了在Android 的Linux 内核空间添加硬件驱动程序、在Android 的硬件抽象层添加硬件接口、在Android
的Application Frameworks 层提供硬件服务以及在Android 的应用层调用硬件服务的整个过程了,希望能为读者进入Android
系统提供入门帮助。重新学习整个过程,请参考Android 硬件抽象层(HAL)概要介绍和学习计划。
本篇文章来源于 Linux 公社网站,原文链接:http://www.linuxidc.com/Linux/2011-07/38980.htm__