關於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