关于Android的HAL的一些理解


之前一直在学习基于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模块的驱动模型图 
这个模型图的结构比较简单,也很清晰。我想大家也很清楚能够看懂。既然这样,为什么还要加入HAL呢,个人认为主要有以下几点原因:

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

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

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

通过上面的一些分析,我想应该也大概对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;

 

而这三个结构体的关系如下图所示: 
HAL层结构图 
下面分别介绍一下这三个结构体 
首先是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结构图 
下面给出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编程的规范,但是相对前者,编写起来稍微复杂一些。后面我也会专门写一篇文章来讲解该方式。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM