之前一直在学习基于Linux内核的一些字符型驱动的编程,对Linux内核驱动也算有了一些基本的了解吧,后来也做过一些基于Linux内核的驱动开发,像基于Android的CC1101高频模块的驱动开发,以及基于V4L2的USB摄像头开发。但是还是一直都没有用到过Android的HAL模块,现在整理一下。 
说到HAL,我想目前市面上关于这方面的书应该也有不少,或者随便到网上一搜,都是一大把。但是作为一个只了解了一点Linux驱动方面的知识,懂一点初级的C语言,要完全了解Android的HAL还是有一定困难的,下面我也就将我在这一块理解的一些心得同大家分享。 
HAL(Hardware Abstraction Layer),中文是硬件抽象层,也就是说是对硬件的一种抽象。在我们之前所开发的Linux驱动程序当中,在编写好驱动程序之后会在/dev的目录下生成相应的设备文件,然后如果该驱动程序是应用在Android系统里面的话,可能还要编写相应的NDK(jni)部分,使之生成动态链接库(.so文件),以方便上层的java程序调用。整个模型如下图所示。 
 
这个模型图的结构比较简单,也很清晰。我想大家也很清楚能够看懂。既然这样,为什么还要加入HAL呢,个人认为主要有以下几点原因:

  1. 协议方面的原因,因为我们知道所有的Linux程序都必须要遵循GPL协议,也就是全部开源协议。而对于有些企业和个人其并不想完全将自己的劳动成果或者是知识产权吧完全公开,所以就在Linux驱动程序的基础之上,在弄了一个HAL层。由于对于一个驱动程序来说,其主要包括两个部分,访问寄存器的代码和业务逻辑代码。而对于访问硬件寄存器的代码,并没有什么秘密可言,无非就是一些Linux内核向寄存器发号施令的标准函数(如ioread32、iowrite32等),所以一个驱动的核心部分应该是在业务逻辑代码上,而开发者将这些业务逻辑的代码放在HAL层,由于HAL层属于用户空间部分,所以并不要遵循Linux的GPL协议,因而HAL便得以广泛应用。

  2. 统一调用接口,我们知道Linux驱动程序的调用接口复杂,不统一,而HAL提供了标准的调用接口,这样很方便

  3. 针对一些特殊要求,例如有些硬件,可能需要访问用户空间的资源,而由于Linux驱动程序是放在内核空间的,所以加入存放用户空间的HAL有利于对用户空间资源的访问。

通过上面的一些分析,我想应该也大概对HAL有一些了解了,现在我给出整个HAL模型图。 
 
看到这个图,大家一定会想,怎么又多了一个Service程序库过来呀,确实在HAL模型刚刚出来的那会儿,的确没有这么一个Service程序库部分。而没有Service程序的HAL构架虽然已经将访问寄存器的代码和业务逻辑代码区分开了,但是其仍然有很多的问题,就是其还是一个孤立与Android系统之外的一个部分,没有与Android系统本身融为一体,根本就没有发挥出HAL的强大优势。所以用于调用HAL程序库的Service层便出现了。 
下面我就以一个非常常见的led灯的例子,来讲解HAL框架。 
第一部分 
Linux内核层(Linux内核驱动程序),相对于没有HAL框架的Linux内核驱动程序,有HAL框架的Linux驱动程序就显得结构比较简单了,无非就是对寄存器的一些简单操作。

#include "s3c6410_leds_hal.h"
#include "leds_hal_define.h" static unsigned char mem[5]; // 第1个字节:GPM寄存器类型,后面4个字节保存GPM寄存器的值
static int major = S3C6410_LEDS_MAJOR;
static int minor = S3C6410_LEDS_MINOR;
static dev_t dev_number; // 设备号
static struct class *leds_class = NULL; //将四个字节转换成int类型的数据,因为从用户空间传递过来的,都是以char数组形式传递的,而如果要在Linux内核使用int类型数据的话,就必须要有这么
//一步
// 只处理从start开始的4个字节,第start个字节为int的最高位
static int bytes_to_int(unsigned char buf[], int start)
{
int n = 0;
n = ((int) buf[start]) << 24 | ((int) buf[start + 1]) << 16
| ((int) buf[start + 2]) << 8 | ((int) buf[start + 3]);
return n;
}
//同样,在处理完用户空间传过来的参数之后,有需要将其转换为byte类型的数据,由于Buf为char类型数组,所以每一次只取低8位数据。
static void int_to_bytes(int n, unsigned char buf[], int start)
{
buf[start] = n >> 24;
buf[start + 1] = n >> 16;
buf[start + 2] = n >> 8;
buf[start + 3] = n;
}
// 向GPM寄存器写数据
static ssize_t s3c6410_leds_hal_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{ if (copy_from_user(mem, buf, 5))
{
return -EFAULT;
}
else
{
int gpm_type = mem[0]; // 获取GPM寄存器类型,这里寄存器的类型放在了buf的第0位。
switch (gpm_type)
{
case S3C6410_LEDS_HAI_WRITE_GPMCON:
iowrite32(bytes_to_int(mem, 1), S3C64XX_GPMCON); //iowrite32是Linux内核标准的库函数,用于向寄存器写数据
break;
case S3C6410_LEDS_HAI_WRITE_GPMPUD:
iowrite32(bytes_to_int(mem, 1), S3C64XX_GPMPUD);
break;
case S3C6410_LEDS_HAI_WRITE_GPMDAT:
iowrite32(bytes_to_int(mem, 1), S3C64XX_GPMDAT);
break;
} }
return 5;
} // 向GPM寄存器写数据
static ssize_t s3c6410_leds_hal_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int gpm_type = mem[0]; // 获取GPM寄存器类型
int gpm_value = 0;
//关于S3C6410_LEDS_HAI_READ_GPMCON这些宏,定义在leds_hal_define.h头文件中
switch (gpm_type)
{
case S3C6410_LEDS_HAI_READ_GPMCON:
gpm_value = ioread32(S3C64XX_GPMCON);
break;
case S3C6410_LEDS_HAI_READ_GPMPUD:
gpm_value = ioread32(S3C64XX_GPMPUD);
break;
case S3C6410_LEDS_HAI_READ_GPMDAT: gpm_value = ioread32(S3C64XX_GPMDAT);
break;
}
int_to_bytes(gpm_value, mem, 1);
if (copy_to_user(buf, (void*) mem, 5))
{
return -EFAULT;
}
return 5;
} //通过file_operation结构指定映射当然是少不了的啦
static struct file_operations dev_fops =
{ .owner = THIS_MODULE, .read = s3c6410_leds_hal_read, .write =
s3c6410_leds_hal_write };
static struct cdev leds_cdev; //创建设备文件(/dev/s3c6410_leds_hal)这是一个规范的创建设备文件的函数。
static int leds_create_device(void)
{
int ret = 0;
int err = 0; // 初始化cdev的成员,并建立cdev和file_operations之间的连接
cdev_init(&leds_cdev, &dev_fops); //这个函数也是Linux的库函数,其作用就是将cdev结构体内部的file_operation成员与dev_fops联系起来
leds_cdev.owner = THIS_MODULE;
if (major > 0)
{
// 获取设备号(主设备号和次设备号)
dev_number = MKDEV(major, minor);
err = register_chrdev_region(dev_number, DEVICE_COUNT, DEVICE_NAME);
if (err < 0)
{
printk(KERN_WARNING "register_chrdev_region() failed\n");
return err;
}
}
else
{
err = alloc_chrdev_region(&leds_cdev.dev, 10, DEVICE_COUNT,
DEVICE_NAME);
if (err < 0)
{
printk(KERN_WARNING "alloc_chrdev_region() failed\n");
return err;
} major = MAJOR(leds_cdev.dev);
minor = MINOR(leds_cdev.dev);
//dev_number = MKDEV(major, minor);
dev_number = leds_cdev.dev; }
ret = cdev_add(&leds_cdev, dev_number, DEVICE_COUNT);
leds_class = class_create(THIS_MODULE, DEVICE_NAME);
device_create(leds_class, NULL, dev_number, NULL, DEVICE_NAME); return ret;
} // 初始化LED驱动
static int leds_init(void)
{
int ret;
ret = leds_create_device();
printk(DEVICE_NAME"\tinitialized\n");
printk(KERN_EMERG"tes1fdddfs1t\n");
return ret;
}
static void leds_destroy_device(void)
{
device_destroy(leds_class, dev_number); if (leds_class)
class_destroy(leds_class);
unregister_chrdev_region(dev_number, DEVICE_COUNT); return; }
static void leds_exit(void)
{
leds_destroy_device();
printk(DEVICE_NAME"\texit!\n");
} module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lining");

上面驱动程序的两个头文件是: 
s3c6410_leds_hal.h

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <mach/map.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank-m.h> #define DEVICE_NAME "s3c6410_leds_hal"
#define DEVICE_COUNT 1 // 设备数量
#define S3C6410_LEDS_MAJOR 0
#define S3C6410_LEDS_MINOR 234

leds_hal_define.h

#define S3C6410_LEDS_HAI_WRITE_GPMPUD 1
#define S3C6410_LEDS_HAI_WRITE_GPMCON 2
#define S3C6410_LEDS_HAI_WRITE_GPMDAT 3
#define S3C6410_LEDS_HAI_READ_GPMPUD 4
#define S3C6410_LEDS_HAI_READ_GPMCON 5
#define S3C6410_LEDS_HAI_READ_GPMDAT 6

由于这里的Linux驱动程序没有采用platform设备驱动模型,所以驱动程序都需要自己编写脚本文件,使之加载进Linux内核。

第二部分 
HAL层,这一层是处于用户空间,这也是为什么,在上面的Linux驱动程序的头文件中s3c6410_leds_hal.h和leds_hal_define.h并没有写在一个文件里面,因为在用户空间中有些Linux内核的头文件,在用户空间是使用不了的。而我们的HAL层则需要leds_hal_define.h这个头文件。 
在给出HAL层的代码之前,我想先简单向大家分析一下HAL层的整个代码结构。而HAL层的结构其实就是由三个关键点结构体贯穿,因此要理解HAL层的代码,只需要了解三个关键的结构体就行,这三个结构体分别是:

struct hw_module_t;
struct hw_module_methods_t;
struct hw_device_t;

而这三个结构体的关系如下图所示: 
 
下面分别介绍一下这三个结构体 
首先是hw_module_t,该结构体用于描述HAL模块,可以说是HAL层的入口,因为想要知道HAL的所有信息,都要通过hw_module_t结构体,上层的Service在调用该HAL模块时,也是要首先找到该模块的module_ID,hw_module_t定义在/hardware/libhardware/include/hardware/hardware.h文件中,其定义如下

typedef struct hw_module_t {
/**模块的tag,值必须是HRADWARE_MODULE_TAG */
uint32_t tag
/**模块主版本号*/
uint16_t module_api_version
/**模块从版本号*/
uint16_t hal_api_version
/**模块的ID号,后面的Service层中的hw_get_module就是通过这个ID号找到该LED模块的*/
const char * id
/**模块名称*/
const char * name
//模块作者
const char * author
/**这个结构体指针就是我们HAL三个关键的结构体之一,与模块相关的函数指针都包含在这个该结构体中*/
struct hw_module_methods_t * methods
void * dso
//保留空间
uint32_t reserved [32-7]
}

由于HAL规定不能直接使用这个结构体,因此我们在实际编写代码过程中,对要对这个结构体进行封装,或者说是做一个结构体的继承吧。 
接下来是hw_device_t,这个结构体便是描述HAL设备的,HAL不是叫硬件抽象层嘛,该结构体便是对这一称谓的集中展示。让我们来看看hw_device_t长什么样,

typedef struct hw_device_t {
//还是设备的tag,HAL规定必须是HARDWARE_MODULE_TAG
uint32_t tag
//设备版本号
uint32_t version
//指向描述该HAL模块的hw_module_t指针
struct hw_module_t * module
//保留的内存空间
uint32_t reserved [12]
//关闭设备函数的指针
int(* close )(struct hw_device_t *device)
}

最后一个便是hw_module_method_t,这个函数呢,是属于前面两个结构体之间的一个桥梁,也相当于HAL设备的一个入口,因为其里面open成员变量函数,通过该函数就可以做一些打开设备文件啦、初始化hw_device_t等工作啦。

typedef struct hw_module_methods_t {
//该结构体唯一的一个成员,一个open函数指针,注意其里面的参数,最后一个参数用了指针的指针,作用很大
//后面会讲到
int(* open )(const struct hw_module_t *module, const char *id, struct hw_device_t **device)
}

接下来就附上完整的HAL层的源代码啦

#include "leds_hal.h"
#include "../leds_hal_define.h" int dev_file = 0; // on_off: 1表示开,0表示关
int led_on_off(struct led_control_device_t *dev, int32_t led, int32_t on_off)
{
if (led >= 0 && led <= 3)
{ if (on_off == 1)
LOGI("LED Stub:set %d on", led);
else
LOGI("LED Stub:set %d off", led);
unsigned char buf[5];
buf[0] = S3C6410_LEDS_HAI_READ_GPMDAT;
write(dev_file, buf, 5);
read(dev_file, buf, 5);
buf[0] = S3C6410_LEDS_HAI_WRITE_GPMDAT; // 修改GPMDAT寄存器的值
switch (led)
{
case 0:
if (on_off == 1) // 打开
buf[4] &= 0xFE; // 11111110
else if (on_off == 0) // 关闭
buf[4] |= 0x1; // 00000001
break;
case 1:
if (on_off == 1) // 打开
{
buf[4] &= 0xFD; // 11111101 }
else if (on_off == 0) // 关闭
{ buf[4] |= 0x2; // 00000010
}
break;
case 2:
if (on_off == 1) // 打开
buf[4] &= 0xFB; // 11111011
else if (on_off == 0) // 关闭
buf[4] |= 0x4; // 00000100 break;
case 3:
if (on_off == 1) // 打开
buf[4] &= 0xF7; // 11110111
else if (on_off == 0) // 关闭
buf[4] |= 0x8; // 00001000
break;
}
//为什么这里只给buf[4]赋值,是由于在Linux驱动程序中的那个bytes_to_int函数导致的。
write(dev_file, buf, 5); }
else
{
LOGI("LED Stub: set led %d on error,no this led", led);
} return 0; } int led_on(struct led_control_device_t *dev, int32_t led)
{
return led_on_off(dev, led, 1);
} int led_off(struct led_control_device_t *dev, int32_t led)
{ return led_on_off(dev, led, 0);
}
int led_device_close(struct hw_device_t* device)
{
struct led_control_device_t* ctx = (struct led_control_device_t*) device;
if (ctx)
{
free(ctx);
}
close(dev_file);
return 0;
}
static void leds_init_gpm()
{
int tmp = 0;
// 初始化端口配置寄存器 unsigned char buf[5];
buf[0] = S3C6410_LEDS_HAI_READ_GPMCON;
write(dev_file, buf, 5);
read(dev_file, buf, 5);
buf[3] |= 0x11;
buf[4] |= 0x11;
buf[0] = S3C6410_LEDS_HAI_WRITE_GPMCON;
write(dev_file, buf, 5); // 初始化端口上拉电路寄存器
buf[0] = S3C6410_LEDS_HAI_READ_GPMPUD;
write(dev_file, buf, 5);
read(dev_file, buf, 5);
buf[4] |= 0xAA;
buf[0] = S3C6410_LEDS_HAI_WRITE_GPMPUD;
write(dev_file, buf, 5); }
static int led_device_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device)
{
struct led_control_device_t *dev;
dev = (struct led_control_device_t *) malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev)); dev->hw_device.tag = HARDWARE_DEVICE_TAG;
dev->hw_device.version = 0;
dev->hw_device.module = (struct hw_module_t*) module;
dev->hw_device.close = led_device_close; dev->set_on = led_on;
dev->set_off = led_off; //*device = &dev->hw_device;
//*dev强制类型转换,即这里向父结构体hw_device_t转换,关于这一点,我会专门写一篇文章来讲解
*device = (hw_device_t*)dev;
//打开设备文件
dev_file = open("/dev/s3c6410_leds_hal", O_RDWR);
if (dev_file < 0)
{
LOGI("LED Stub: open /dev/s3c6410_leds_hal fail.");
}
else
{
LOGI("LED Stub: open /dev/s3c6410_leds_hal success .");
}
leds_init_gpm();
return 0;
}
//将open函数指针指向自定义的led_device_open
static struct hw_module_methods_t led_module_methods =
{ open: led_device_open }; /**
这里给led_module_t结构体变量命名只能是HAL_MODULE_INFO_SYM,这是HAL规定的。
*/
struct led_module_t HAL_MODULE_INFO_SYM =
{ hw_module:
{ tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id
: LED_HARDWARE_MODULE_ID,
name: "Sample LED HAL Stub",
author: "Lining",
//初始化led_module_method
methods: &led_module_methods,
}
};

上面的HAL程序用到的头文件源码 
led_hal.h文件

#include <hardware/hardware.h>
#include <fcntl.h>
#include <cutils/log.h> struct led_module_t
{
struct hw_module_t hw_module;
};
struct led_control_device_t
{
//注意,led_control_device_t结构体的第一个成员变量必须要是hw_device_t,这是由于后面会有结构体向父结构体强制类型转换。
struct hw_device_t hw_device;
//额外定义了两个函数指针,这也是该结构体的关键所在
int (*set_on)(struct led_control_device_t *dev, int32_t led);
int (*set_off)(struct led_control_device_t *dev, int32_t led);
};
//在这里定义了该HAL模块的ID号,后面的service就是通过这个ID来找到这个HAL模块的。
#define LED_HARDWARE_MODULE_ID "led_hal"

这里还要提一下HAL层代码的编译问题,该层代码最终要编译成led_hal.default.so文件,并且要使用adb工具将该.so文件上传至开发板的/system/lib/hw目录下,HAL模块的.so文件一般都是放在这个目录下,后面会解释为什么这么放置,同时也会解释为什么该.so文件后面会有一个default的后缀

第三部分,Service层 
终于可以讲到Service Library层啦,Service Library层应该是上层的应用程序访问HAL层的一个桥梁,尽管在以前的旧HAL框架上,并没有这一部分,但新的HAL框架都需要我们加入Servicer Library。刚刚我们说了Service Library是上层Android应用程序和下层HAL层的一个连接的桥梁,那么它是如何发挥桥梁作用的呢?因为要完成对HAL的访问,我们不能像之前上层应用程序访问设备文件一样,通过open函数打开设备文件,然后返回该文件的句柄,最后再通过该句柄完成其它函数的操作。而HAL层是通过hw_module_t这样一个结构体对外提供接口,因此在Service Library我们要使用一个非常重要的函数hw_get_module函数,通过该函数就可以在上面的led_hal.h文件中所定义的LED_HARDWARE_MODULE_ID来查找相应的LED HAL模块。我们知道刚刚上面的HAL层,我们都是采用C语言编写的,而上层应用程序有是用JAVA编写,因此这里我们应该需要有一个JNI Library(也就是NDK程序),来完成C语言和JAVA之间的对接。 
 
下面给出Service Library层的源代码

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <jni.h>
#include <leds_hal.h>
/**这里说明一下为什么这里的leds_hal.h文件会用尖括号<...>,这是因为该文件属于HAL的源代码文件,其路径在hardware/leds_hal上,且在其对应的Android.mk文件中指定,因此该文件便属于系统文件,所以需要用尖括号表示。*/
struct led_control_device_t *led_hal_device = NULL;
/**定义led_control_device_t结构体指针,后面的led_control_open函数会用到。这个结构体包含了HAL模块中许多重要函数,像后面要调用的set_on和set_off等*/
static jboolean led_setOn(JNIEnv* env, jobject thiz, jint led)
{
LOGI("Led HAL JNI: led_setOn() is invoked."); if (led_hal_device == NULL)
{
LOGI("Led HAL JNI: led_hal_device was not fetched correctly.");
return -1;
}
else
{
return led_hal_device->set_on(led_hal_device, led);
}
} static jboolean led_setOff(JNIEnv* env, jobject thiz, jint led)
{
LOGI("Led HAL JNI: led_setOff() is invoked.");
if (led_hal_device == NULL)
{
LOGI("Led HAL JNI: led_hal_device was not fetched correctly.");
return -1;
}
else
{
return led_hal_device->set_off(led_hal_device, led);
}
}
//定义一个内敛函数,以调用HAL的open方法
static inline int led_control_open(const 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);
} static jboolean led_init(JNIEnv *env, jclass clazz)
{
led_module_t* module; LOGE("**********start find hal *********");
LOGE(LED_HARDWARE_MODULE_ID);
/**通过该函数以及HAL的ID号,返回相应的hw_module_t指针,hw_get_module函数在hardware.c中定义*/
if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**) &module)
== 0)
{
LOGI("LedService JNI: LED Stub found.");
if (led_control_open(&module->hw_module, &led_hal_device) == 0)
{
LOGI("LedService JNI: Got Stub operations.");
return 0;
}
} LOGE("LedService JNI: Get Stub operations failed.");
return -1;
} /**与上层的Java库进行方法映射,即定义一个method数组。如果不使用这种方法映射的方式的话,就要使用JNI的特殊命名方式,之前我们在写CC1101的时候就是采用的那种方式。*/
static const JNINativeMethod methods[] =
{
{ "_init", "()Z", (void *) led_init },
{ "_set_on", "(I)Z", (void *) led_setOn },
{ "_set_off", "(I)Z", (void *) led_setOff }, }; /**通过上面的映射之外,我们还需要RegisterNative注册,才能发挥效力*/
int register_led_hal_jni(JNIEnv* env)
{
//这里要事先定义好Java库的路径
static const char* const kClassName = "mobile/android/leds/hal/service/LedHalService"; jclass clazz; clazz = env->FindClass(kClassName);
if (clazz == NULL)
{
LOGE("Can't find class %s\n", kClassName);
return -1;
}
if (env->RegisterNatives(clazz, methods,
sizeof(methods) / sizeof(methods[0])) != JNI_OK)
{
LOGE("Failed registering methods for %s\n", kClassName);
return -1;
} return 0;
} /**通过该函数,会初始化该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)
{
LOGE("GetEnv failed!");
return result;
} register_led_hal_jni(env); return JNI_VERSION_1_4;
}

下面附上Android.mk程序

# Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
//定义该共享库的名称,因而后面就会生成.so文件
LOCAL_MODULE:= led_hal_jni
#指定编译完成后,其.so文件存放的路径,如果不指定,就会编译进默认目录
LOCAL_MODULE_PATH := /root/drivers/s3c6410_leds_hal/leds_hal_jni
#指定源文件
LOCAL_SRC_FILES:= LedHalService.cpp #指定共享库的位置
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libcutils \
libhardware \
libhardware_legacy \
libnativehelper \
libsystem_server \
libutils \
libui \
libsurfaceflinger_client #指定头文件位置(或者叫头文件的搜索路径)这里有两个路径,JNI_H_INCLUDE和hardware/leds_hal
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
hardware/leds_hal
#指定预链接模式
LOCAL_PRELINK_MODULE := false
#生成共享库(.so文件)
include $(BUILD_SHARED_LIBRARY)

用于编译的build.sh脚本

source ~/drivers/common.sh
cd $OK6410_ANDROID_SRC_PATH
source ./build/envsetup.sh
cd $OK6410_ANDROID_SRC_PATH/frameworks/base/services/leds_hal_jni
mm
cd /root/drivers/s3c6410_leds_hal/leds_hal_jni # cp $OK6410_ANDROID_SRC_PATH/out/target/product/generic/obj/lib/led_hal_jni.so . find_devices
if [ "$selected_device" == "" ]; then
exit
else
#将生成的led_hal_jni.so文件上传至/system/lib目录下
adb -s $selected_device push ./led_hal_jni.so /system/lib | echo "已成功上传到$selected_device"
fi

到现在为止基于HAL的LED驱动就差不多编译完成了,其实到现在就可以在你的Android应用程序当中通过NDK调用上一步的Service Library,从而调用后面的HAL和驱动程序。当然我们在这一步也可以采用更加灵活,或者说是更易于应用程序使用的方式,即将调用Service程序库的java类封装在jar文件中,这样做,任何Android应用程序只要引用了这个jar文件就可以向调用普通Java类一样访问LED驱动了。当然这里还有另外一种方式就是采用ServiceManager的方式,此种方式更加符合目前主流Android编程的规范,但是相对前者,编写起来稍微复杂一些。后面我也会专门写一篇文章来讲解该方式。

关于Android的HAL的一些理解的更多相关文章

  1. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  2. [转]Android View.onMeasure方法的理解

    转自:http://blog.sina.com.cn/s/blog_61fbf8d10100zzoy.html Android View.onMeasure方法的理解 View在屏幕上显示出来要先经过 ...

  3. android线程池ThreadPoolExecutor的理解

    android线程池ThreadPoolExecutor的理解 线程池 我自己理解看来.线程池顾名思义就是一个容器的意思,容纳的就是ThreadorRunable, 注意:每一个线程都是需要CPU分配 ...

  4. 【转】【Android】HAL分析

    原文网址:http://www.cnblogs.com/lcw/p/3335505.html HAL概述 以下是基于android4.0.3,对应其他低版本的代码,可能有所差异,但基本大同小异. An ...

  5. android的事件分发机制理解

    android的事件分发机制理解 1.事件触发主要涉及到哪些层面的哪些函数(个人理解的顺序,可能在某一层会一次回调其它函数) activity中的dispatchTouchEvent .layout中 ...

  6. Android高手进阶——Adapter深入理解与优化

    Android高手进阶--Adapter深入理解与优化 通常是针对包括多个元素的View,如ListView,GridView.ExpandableListview,的时候我们是给其设置一个Adapt ...

  7. 【Android】HAL分析

    HAL概述 以下是基于android4.0.3,对应其他低版本的代码,可能有所差异,但基本大同小异. Android的HAL是为了保护一些硬件提供商的知识产权而提出的,是为了避开linux的GPL束缚 ...

  8. Android系统HAL基本概念

    1.前言 Android系统硬件抽象层(Hardware Abstraction Layer),简写为HAL,是连接Android Framework与Linux内核设备驱动的重要桥梁.HAL存在的意 ...

  9. Android:Layout_weight的深刻理解

    最近写Demo,突然发现了Layout_weight这个属性,发现网上有很多关于这个属性的有意思的讨论,可是找了好多资料都没有找到一个能够说的清楚的,于是自己结合网上资料研究了一下,终于迎刃而解,写出 ...

随机推荐

  1. uboot之run_command简单分析

    本文档简单分析了uboot中命令的实现.run_command函数的实现以及从uboot命令行接收并处理命令的过程. 作者: 彭东林 邮箱: pengdonglin137@163.com http:/ ...

  2. Linux下Kafka单机安装配置方法

    Kafka是一个分布式的.可分区的.可复制的消息系统.它提供了普通消息系统的功能,但具有自己独特的设计.这个独特的设计是什么样的呢? 首先让我们看几个基本的消息系统术语: •Kafka将消息以topi ...

  3. p2p webrtc服务器搭建系列1: 房间,信令,coturn打洞服务器

    中继(relay) 在RTCPeeConnection中,使用ICE框架来保证RTCPeerConnection能实现NAT穿越 ICE,全名叫交互式连接建立(Interactive Connecti ...

  4. bugzilla 系列1安装

    安装好mysql yum install gcc perl* mod_perl-devel -y wget https://ftp.mozilla.org/pub/mozilla.org/webtoo ...

  5. [Python]xlrd 读取excel 日期类型2种方式

    有个excle表格须要做一些过滤然后写入数据库中,可是日期类型的cell取出来是个数字,于是查询了下解决的办法. 主要的代码结构 data = xlrd.open_workbook(EXCEL_PAT ...

  6. RSA加密、解密、公钥私钥生成

    有时项目中需要用到一些加密和解密工具,这里之前整理了一个demo,记录一下,方便查询 package com.test; import java.security.KeyFactory; import ...

  7. 【题解】[APIO2009]会议中心

    [题解][P3626 APIO2009]会议中心 真的是一道好题!!!刷新了我对倍增浅显的认识. 此题若没有第二份输出一个字典序的方案,就是一道\(sort+\)贪心,但是第二问使得我们要用另外的办法 ...

  8. 一起来学linux:sudo

    通常在转换用户的时候会用到su 用户的方式.但是su方式需要知道切换的用户密码.而且su root到roo账户后,root账户有全部的权限.为了防止root账户干错事,因此有了sudo的命令.sudo ...

  9. phpPHP创建创建jpg格式图片以及压缩图片(转)

    其实是因为一些业务上的需求,所以需要对用户上传后的图片进行压缩,因为上传的图片比较大,显示的时候加载起来如果网速不给力的话就很吃力了,而且大图片也浪费空间,于是找了一下相关的资源,主要方法在开源中国上 ...

  10. Java for LeetCode 129 Sum Root to Leaf Numbers

    Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number ...