本文基于Android5.0开发APP控制硬件LED亮灭,实现应用程序、应用程序框架层、系统运行时层、硬件抽象层、linux字符设备驱动的开发过程。

上图描述了我们Led实例的框架层次:

l  LedDemo.java:是我们写的Android应用程序

l  LedService.java:是根据Led HAL封装的Java框架层的API,主要用于向应用层提供框架层API,它属于Android的框架层

l  libled_runtime.so:由于Java代码不能访问HAL层,该库是LedService.java对应的本地代码部分

l  led.default.so:针对led硬件的HAL代码

LedDemo通过LedService提供的框架层API访问Led设备,LedService对于LedDemo应用程序而言是Led设备的服务提供者,LedService运行在Dalvik中没有办法直接访问Led硬件设备,它只能将具体的Led操作交给本地代码来实现,通过JNI来调用Led硬件操作的封装库libled_runtime.so,由HAL Stub框架可知,在libled_runtime.so中首先查找注册为led的硬件设备module,找到之后保存其操作接口指针在本地库中等待框架层LedService调用。led.default.so是HAL层代码,它是上层操作的具体实施者,它并不是一个动态库(也就是说它并没有被任何进程加载并链接),它只是在本地代码查找硬件设备module时通过ldopen”杀鸡取卵”找module,返回该硬件module对应的device操作结构体中封装的函数指针。

其调用时序如下:

Led HAL实例代码分析

我们来看下led实例的目录结构:

主要文件如下:

com.hello.LedService.cpp:它在frameworks/services/jni目录下,是的Led本地服务代码

led.c:HAL代码

led.h:HAL代码头文件

LedDemo.java:应用程序代码

LedService.java:Led框架层服务代码

在Android的源码目录下,框架层服务代码应该放在frameworks/services/java/包名/目录下,由Android的编译系统统一编译生成system/framework/services.jar文件,由于我们的测试代码属于厂商定制代码,尽量不要放到frameworks的源码树里,我将其和LedDemo应用程序放在一起了,虽然这种方式从Android框架层次上不标准。

另外,本地服务代码的文件名要和对应的框架层Java代码的名字匹配(包名+类文件名,包目录用“_“代替)。有源码目录里都有对应的一个Android.mk文件,它是Android编译系统的指导文件,用来编译目标module。

1)        Android.mk文件分析

先来看下led源码中①号Android.mk:

[plain] view plain copy

  1. include $(call all-subdir-makefiles)

代码很简单,表示包含当前目录下所有的Android.mk文件

先来看下led_app目录下的③号Android.mk:

[plain] view plain copy

  1. # 调用宏my-dir,这个宏返回当前Android.mk文件所在的路径
  2. LOCAL_PATH:= $(call my-dir)
  3. # 包含CLEAR_VARS变量指向的mk文件build/core/clear_vars.mk,它主要用来清除编译时依赖的编译变量
  4. include $(CLEAR_VARS)
  5. # 指定当前目标的TAG标签,关于其作用见前面Android编译系统章节
  6. LOCAL_MODULE_TAGS := user
  7. # 当前mk文件的编译目标模块
  8. LOCAL_PACKAGE_NAME := LedDemo
  9. # 编译目标时依赖的源码,它调用了一个宏all-java-files-under,该宏在build/core/definitions.mk中定义
  10. # 表示在当前目录下查找所有的java文件,将查找到的java文件返回
  11. LOCAL_SRC_FILES := $(callall-java-files-under, src)
  12. # 在编译Android应用程序时都要指定API level,也就是当前程序的编译平台版本
  13. # 这里表示使用当前源码的版本
  14. LOCAL_SDK_VERSION := current
  15. # 最重要的就是这句代码,它包含了一个文件build/core/package.mk,根据前面设置的编译变量,编译生成Android包文件,即:apk文件
  16. include $(BUILD_PACKAGE)

上述代码中都加了注释,基本上每一个编译目标都有类似上述的编译变量的声明:

LOCAL_MODULE_TAGS

LOCAL_PACKAGE_NAME

LOCAL_SRC_FILES

由于所有的Android.mk最终被编译系统包含,所以在编译每个目标模块时,都要通过LOCAL_PATH:= $(call my-dir)指定当前目标的目录,然后调用include $(CLEAR_VARS)先清除编译系统依赖的重要的编译变量,再生成新的编译变量。

让我们来看看LedDemo目标对应的源码吧。

2)        LedDemo代码分析

学习过Android应用的同学对其目录结构很熟悉,LedDemo的源码在src目录下。

@ led_app/src/com/farsight/LedDemo.java:

[java] view plain copy

  1. package com.hello;
  2. import com.hello.LedService;
  3. import com.hello.R;
  4. importandroid.app.Activity;
  5. importandroid.os.Bundle;
  6. importandroid.util.Log;
  7. importandroid.view.View;
  8. import android.view.View.OnClickListener;
  9. importandroid.widget.Button;
  10. public classLedDemo extends Activity {
  11. privateLedService led_svc;
  12. private Buttonbtn;
  13. private booleaniflag = false;
  14. private Stringtitle;
  15. /** Calledwhen the activity is first created. */
  16. @Override
  17. public void onCreate(Bundle savedInstanceState) {
  18. super.onCreate(savedInstanceState);
  19. setContentView(R.layout.main);
  20. Log.i("Java App", "OnCreate");
  21. led_svc =new LedService();
  22. btn =(Button) this.findViewById(R.id.Button01);
  23. this.btn.setOnClickListener(new OnClickListener() {
  24. public void onClick(View v) {
  25. Log.i("Java App", "btnOnClicked");
  26. if (iflag) {
  27. title = led_svc.set_off();
  28. btn.setText("Turn On");
  29. setTitle(title);
  30. iflag = false;
  31. else {
  32. title = led_svc.set_on();
  33. btn.setText("Turn Off");
  34. setTitle(title);
  35. iflag = true;
  36. }
  37. }
  38. });
  39. }
  40. }

代码很简单,Activity上有一个按钮,当Activity初始化时创建LedService对象,按钮按下时通过LedService对象调用其方法set_on()和set_off()。

3)        LedService代码分析

我们来看下LedService的代码:

@led_app/src/com/farsight/LedService.java:

[java] view plain copy

  1. package com.hello;
  2. import android.util.Log;
  3. public class LedService {
  4. /*
  5. * loadnative service.
  6. */
  7. static {         // 静态初始化语言块,仅在类被加载时被执行一次,通常用来加载库
  8. Log.i ("Java Service" , "Load Native Serivce LIB" );
  9. System.loadLibrary ( "led_runtime" );
  10. }
  11. // 构造方法
  12. public LedService() {
  13. int icount ;
  14. Log.i ("Java Service" , "do init Native Call" );
  15. _init ();
  16. icount =_get_count ();
  17. Log.d ("Java Service" , "led count = " + icount );
  18. Log.d ("Java Service" , "Init OK " );
  19. }
  20. /*
  21. * LED nativemethods.
  22. */
  23. public Stringset_on() {
  24. Log.i ("com.hello.LedService" , "LED On" );
  25. _set_on();
  26. return"led on" ;
  27. }
  28. public String set_off() {
  29. Log.i ("com.hello.LedService" , "LED Off" );
  30. _set_off();
  31. return"led off" ;
  32. }
  33. /*
  34. * declare all the native interface.
  35. */
  36. private static native boolean _init();
  37. private static native int _set_on();
  38. private static native int _set_off();
  39. private static native int _get_count();
  40. }

通过分析上面代码可知LedService的工作:

l   加载本地服务的库代码

l   在构造方法里调用_init本地代码,对Led进行初始化,并调用get_count得到Led灯的个数

l   为LedDemo应用程序提供两个API:set_on和set_off,这两个API方法实际上也是交给了本地服务代码来操作的

由于Java代码无法直接操作底层硬件,通过JNI方法将具体的操作交给本地底层代码实现,自己只是一个API Provider,即:服务提供者。

让我们来到底层本地代码,先看下底层代码的Android.mk文件:

@ frameworks/Android.mk:

[plain] view plain copy

  1. LOCAL_PATH:= $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE_TAGS := eng
  4. LOCAL_MODULE:= libled_runtime                    # 编译目标模块
  5. LOCAL_SRC_FILES:= \
  6. services/jni/com_farsight_LedService.cpp
  7. LOCAL_SHARED_LIBRARIES := \                       # 编译时依赖的动态库
  8. libandroid_runtime  \
  9. libnativehelper    \
  10. libcutils         \
  11. libutils          \
  12. libhardware
  13. LOCAL_C_INCLUDES += \                                  #编译时用到的头文件目录
  14. $(JNI_H_INCLUDE)
  15. LOCAL_PRELINK_MODULE := false                            # 本目标为非预链接模块
  16. include $(BUILD_SHARED_LIBRARY)                # 编译生成共享动态库

结合前面分析的Android.mk不难看懂这个mk文件。之前的mk文件是编译成Android apk文件,这儿编译成so共享库,所以LOCAL_MODULE和include $(BUILD_SHARED_LIBRARY)与前面mk文件不同,关于Android.mk文件里的变量作用,请查看Android编译系统章节。

总而言之,本地代码编译生成的目标是libled_runtime.so文件。

4)        Led本地服务代码分析

我们来看下本地服务的源码:

@ frameworks/services/jni/com_farsight_LedService.cpp:

[cpp] view plain copy

  1. #define LOG_TAG "LedService"
  2. #include "utils/Log.h"
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <assert.h>
  7. #include <jni.h>
  8. #include "../../../hardware/led.h"
  9. static led_control_device_t *sLedDevice = 0;
  10. static led_module_t* sLedModule=0;
  11. static jint get_count(void)
  12. {
  13. LOGI("%sE", __func__);
  14. if(sLedDevice)
  15. returnsLedDevice->get_led_count(sLedDevice);
  16. else
  17. LOGI("sLedDevice is null");
  18. return 0;
  19. }
  20. static jint led_setOn(JNIEnv* env, jobject thiz) {
  21. LOGI("%sE", __func__);
  22. if(sLedDevice) {
  23. sLedDevice->set_on(sLedDevice);
  24. }else{
  25. LOGI("sLedDevice is null");
  26. }
  27. return 0;
  28. }
  29. static jint led_setOff(JNIEnv* env, jobject thiz) {
  30. LOGI("%s E", __func__);
  31. if(sLedDevice) {
  32. sLedDevice->set_off(sLedDevice);
  33. }else{
  34. LOGI("sLedDevice is null");
  35. }
  36. return 0;
  37. }
  38. static inline int led_control_open(const structhw_module_t* module,
  39. structled_control_device_t** device) {
  40. LOGI("%s E ", __func__);
  41. returnmodule->methods->open(module,
  42. LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
  43. }
  44. static jint led_init(JNIEnv *env, jclass clazz)
  45. {
  46. led_module_tconst * module;
  47. LOGI("%s E ", __func__);
  48. if(hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0){
  49. LOGI("get Module OK");
  50. sLedModule = (led_module_t *) module;
  51. if(led_control_open(&module->common, &sLedDevice) != 0) {
  52. LOGI("led_init error");
  53. return-1;
  54. }
  55. }
  56. LOGI("led_init success");
  57. return 0;
  58. }
  59. /*
  60. *
  61. * Array ofmethods.
  62. * Each entryhas three fields: the name of the method, the method
  63. * signature,and a pointer to the native implementation.
  64. */
  65. static const JNINativeMethod gMethods[] = {
  66. {"_init",    "()Z",(void*)led_init},
  67. {"_set_on",   "()I",(void*)led_setOn },
  68. {"_set_off", "()I",(void*)led_setOff },
  69. {"_get_count", "()I",(void*)get_count },
  70. };
  71. static int registerMethods(JNIEnv* env) {
  72. static constchar* const kClassName = "com/hello/LedService";
  73. jclass clazz;
  74. /* look upthe class */
  75. clazz =env->FindClass(kClassName);
  76. if (clazz ==NULL) {
  77. LOGE("Can't find class %s\n", kClassName);
  78. return-1;
  79. }
  80. /* registerall the methods */
  81. if(env->RegisterNatives(clazz, gMethods,
  82. sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
  83. {
  84. LOGE("Failed registering methods for %s\n", kClassName);
  85. return -1;
  86. }
  87. /* fill outthe rest of the ID cache */
  88. return 0;
  89. }
  90. /*
  91. * This iscalled by the VM when the shared library is first loaded.
  92. */
  93. jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  94. JNIEnv* env= NULL;
  95. jint result= -1;
  96. LOGI("JNI_OnLoad");
  97. if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
  98. LOGE("ERROR: GetEnv failed\n");
  99. gotofail;
  100. }
  101. assert(env!= NULL);
  102. if(registerMethods(env) != 0) {
  103. LOGE("ERROR: PlatformLibrary nativeregistration failed\n");
  104. gotofail;
  105. }
  106. /* success-- return valid version number */
  107. result =JNI_VERSION_1_4;
  108. fail:
  109. return result;
  110. }

这儿的代码不太容易读,因为里面是JNI的类型和JNI特性的代码,看代码先找入口。LedService.java框架代码一加载就调用静态初始化语句块里的System.loadLibrary ( "led_runtime" ),加载libled_runtime.so,该库刚好是前面Android.mk文件的目标文件,也就是说LedService加载的库就是由上面的本地代码生成的。当一个动态库被Dalvik加载时,首先在Dalvik会回调该库代码里的JNI_OnLoad函数。也就是说JNI_OnLoad就是本地服务代码的入口函数。

JNI_OnLoad的代码一般来说是死的,使用的时候直接拷贝过来即可,vm->GetEnv会返回JNIEnv指针,而这个指针其实就是Java虚拟机的环境变量,我们可以通过该指针去调用JNI提供的方法,如FindClass等,调用registerMethods方法,在方法里通过JNIEnv的FindClass查找LedService类的引用,然后在该类中注册本地方法与Java方法的映射关系,上层Java代码可以通过这个映射关系调用到本地代码的实现。RegisterNatives方法接收三个参数:

l  第一个参数jclass:要注册哪个类里的本地方法映射关系

l  第二个参数JNINativeMethod*:这是一个本地方法与Java方法映射数组,JNINativeMethod是个结构体,每个元素是一个Java方法到本地方法的映射。

[cpp] view plain copy

  1. typedef struct {
  2. constchar* name;
  3. constchar* signature;
  4. void*fnPtr;
  5. } JNINativeMethod;

name:表示Java方法名

signature:表示方法的签名

fnPtr:Java方法对应的本地方法指针

l  第三个参数size:映射关系个数

由代码可知,Java方法与本地方法的映射关系如下:

Java方法

本地方法

void _init()

jint led_init(JNIEnv *env, jclass clazz)

int _set_on()

jint led_setOn(JNIEnv* env, jobject thiz)

int _set_off()

jint led_setOff(JNIEnv* env, jobject thiz)

int _get_count()

jint get_count(void)

通过上表可知,本地方法参数中默认会有两个参数:JNIEnv* env, jobject thiz,分别表示JNI环境和调用当前方法的对象引用,当然你也可以不设置这两个参数,在这种情况下你就不能访问Java环境中的成员。本地方法与Java方法的签名必须一致,返回值不一致不会造成错误。

现在我们再来回顾下我们的调用调用流程:

l  LedDemo创建了LedService对象

l  LedService类加载时加载了对应的本地服务库,在本地服务库里Dalvik自动调用JNI_OnLoad函数,注册Java方法和本地方法映射关系。

根据Java语言特点,当LedDemo对象创建时会调用其构造方法LedService()。

[cpp] view plain copy

  1. // 构造方法
  2. public LedService() {
  3. int icount ;
  4. Log.i ("Java Service" , "do init Native Call" );
  5. _init ();
  6. icount =_get_count ();
  7. Log.d ("Java Service" , "led count = " + icount );
  8. Log.d ("Java Service" , "Init OK " );
  9. }

在LedService构造方法里直接调用了本地方法_init和_get_count(通过native保留字声明),也就是说调用了本地服务代码里的jint led_init(JNIEnv *env, jclass clazz)和jintget_count(void)。

在led_init方法里的内容就是我们前面分析HAL框架代码的使用规则了。

l  通过hw_get_module方法查到到注册为LED_HARDWARE_MODULE_ID,即:”led”的module模块。

l  通过与led_module关联的open函数指针打开led设备,返回其device_t结构体,保存在本地代码中,有的朋友可能会问,不是本地方法不能持续保存一个引用吗?由于device_t结构是在open设备时通过malloc分配的,只要当前进程不死,该指针一直可用,在这儿本地代码并没有保存Dalvik里的引用,保存的是mallco的分配空间地址,但是在关闭设备时记得要将该地址空间free了,否则就内存泄漏了。

l  拿到了led设备的device_t结构之后,当LedDemo上的按钮按下时调用LedService对象的set_on和set_off方法,这两个LedService方法直接调用了本地服务代码的对应映射方法,本地方法直接调用使用device_t指向的函数来间接调用驱动操作代码。

好吧,让我们再来看一个详细的时序图:

不用多解释了。

最后一个文件,HAL对应的Android.mk文件:

@ hardware/Android.mk:

[plain] view plain copy

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_C_INCLUDES += \
  4. include/
  5. LOCAL_PRELINK_MODULE := false
  6. LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
  7. LOCAL_SHARED_LIBRARIES := liblog
  8. LOCAL_SRC_FILES := led.c
  9. LOCAL_MODULE := led.default
  10. include $(BUILD_SHARED_LIBRARY)

注:LOCAL_PRELINK_MODULE:= false要加上,否则编译出错

指定目标名为:led.default

目标输入目录LOCAL_MODULE_PATH为:/system/lib/hw/,不指定会默认输出到/system/lib目录下。

根据前面HAL框架分析可知,HAL Stub库默认加载地址为:/vendor/lib/hw/或/system/lib/hw/,在这两个目录查找:硬件id名.default.so,所以我们这儿指定了HAL Stub的编译目标名为led.default,编译成动态库,输出目录为:$(TARGET_OUT_SHARED_LIBRARIES)/hw,TARGET_OUT_SHARED_LIBRARIES指/system/lib/目录。

5) 深入理解

我们从进程空间的概念来分析下我们上面写的代码。

我们前面的示例代码中,将LedDemo.java和LedService.java都放在了一个APK文件里,这也就意味着这个应用程序编译完之后,它会运行在一个Dalvik虚拟机实例中,即:一个进程里,在LedService.java中加载了libled_runtime.so库,通过JNI调用了本地代码,根据动态库的运行原理,我们知道,libled_runtime.so在第一次引用时会被加载到内存中并映射到引用库的进程空间中,我们可以简单理解为引用库的程序和被引用的库在一个进程中,而在libled_runtime.so库中,又通过dlopen打开了库文件led.default.so(该库并没有被库加载器加载,而是被当成一个文件打开的),同样我们可以理解为led.default.so和libled_runtime.so在同一个进程中。

由此可见,上面示例的Led HAL代码全部都在一个进程中实现,在该示例中的LedService功能比较多余,基本上不能算是一个服务。如果LedDemo运行在两个进程中,就意味着两个进程里的LedService不能复用,通常我们所谓的Service服务一般向客户端提供服务并且同时可以为多个客户端服务(如下图),所以我们的示例Led HAL代码不是完美的HAL模型,我们后面章节会再实现一个比较完美的HAL架构。

Led驱动及Makefile源码:

/**************************** 3  Linux底层驱动*****************************************/fspad733_led驱动:#include "fspad723_led.h"#define FSPAD723_GPFCON    0x01C208B4
#define FSPAD723_GPFDAT    0x01C208C4MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("farsight");static int led_major = 500;
static int led_minor = 0;
static struct cdev led_cdev;static unsigned int *gpfcon;
static unsigned int *gpfdat;struct class *led_class;static void led_init(void)
{writel((readl(gpfcon) & ~(0xf << 8)) | (0x1 << 8), gpfcon);
}static void led_recovery(void)
{writel((readl(gpfcon) & ~(0xf << 8)) | (0x3 << 8), gpfcon);
}static void led_on(void)
{writel(readl(gpfdat) & ~(0x1 << 2), gpfdat);
}static void led_off(void)
{writel(readl(gpfdat) | (0x1 << 2), gpfdat);
}static int fspad723_led_open(struct inode *inode, struct file *file)
{printk("led: device open\n");led_init();return 0;
}static int fspad723_led_close(struct inode *inode, struct file *file)
{printk("led: device close\n");led_recovery();return 0;
}static long fspad723_led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{printk("led: device ioctl\n");switch(cmd){case LED_ON:printk("led: ON\n");led_on();break;case LED_OFF:printk("led: OFF\n");led_off();break;default:printk("led: available command\n");break;}return 0;
}static struct file_operations fspad723_led_ops = {.owner         = THIS_MODULE,.open         = fspad723_led_open,.release     = fspad723_led_close,.unlocked_ioctl        = fspad723_led_ioctl
};static int led_setup_cdev(struct cdev *cdev, struct file_operations *fops)
{int result;dev_t devno = MKDEV(led_major, led_minor);cdev_init(cdev, fops);cdev->owner = THIS_MODULE;result = cdev_add(cdev, devno, 1);if(result){printk("led: cdev add failed\n");return result;}return 0;
}static ssize_t fspad723_led_enable_show(struct device *dev,struct device_attribute *attr, char *buf)
{return 0;
}static ssize_t fspad723_led_enable_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count)
{unsigned long data;int error;error = strict_strtoul(buf, 10, &data);printk("set value = %d\n", data);if (error)return error;if (data == 1) {printk("turn on\n");led_init();led_on();}else {printk("turn off\n");led_off();led_recovery();}return count;
}static DEVICE_ATTR(enable, 0777,fspad723_led_enable_show, fspad723_led_enable_store);static struct attribute *fspad723_led_attributes[] = {&dev_attr_enable.attr,NULL,
};static struct attribute_group fspad723_led_attributes_group = {.attrs = fspad723_led_attributes
};struct device *dev;static int __init fspad723_led_init(void)
{int result;dev_t devno = MKDEV(led_major, led_minor);result = register_chrdev_region(devno, 1, "fspad723_led");if(result){printk("led: unable to get major %d\n", led_major);return result;}result = led_setup_cdev(&led_cdev, &fspad723_led_ops);if(result) {printk("led: failed in cdev add.\n");unregister_chrdev_region(devno, 1);return result;}gpfcon = ioremap(FSPAD723_GPFCON, 4);if (gpfcon == NULL) {printk("led: failed in ioremap.\n");goto err1;}gpfdat = ioremap(FSPAD723_GPFDAT, 4);if (gpfcon == NULL) {printk("led: failed in ioremap.\n");goto err2;}led_class = class_create(THIS_MODULE, "farsight_led");if (IS_ERR(led_class)) {printk("led: failed in creating class.\n");goto err3;}dev = device_create(led_class, NULL, devno, NULL, "led");result = sysfs_create_group(&dev->kobj,&fspad723_led_attributes_group);if (result < 0){printk("led: sysfs_create_group err\n");goto err3;}printk("led: driver installed, with major %d!\n", led_major);return 0;
err3:iounmap(gpfdat);
err2:iounmap(gpfcon);
err1:cdev_del(&led_cdev);unregister_chrdev_region(devno, 1);return result;
}static void __exit fspad723_led_exit(void)
{dev_t devno = MKDEV(led_major, led_minor);sysfs_remove_group(&dev->kobj, &fspad723_led_attributes_group);device_destroy(led_class, devno);class_destroy(led_class);iounmap(gpfdat);iounmap(gpfcon);cdev_del(&led_cdev);unregister_chrdev_region(devno, 1);printk("led: driver uninstalled!\n");
}module_init(fspad723_led_init);
module_exit(fspad723_led_exit);/驱动Makefile//
#
# Makefile for the kernel character device drivers.
#
obj-m                += fspad723_led.o#include "fspad723_led.h"#define FSPAD723_GPFCON    0x01C208B4
#define FSPAD723_GPFDAT    0x01C208C4MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("farsight");static int led_major = 500;
static int led_minor = 0;
static struct cdev led_cdev;static unsigned int *gpfcon;
static unsigned int *gpfdat;struct class *led_class;static void led_init(void)
{writel((readl(gpfcon) & ~(0xf << 8)) | (0x1 << 8), gpfcon);
}static void led_recovery(void)
{writel((readl(gpfcon) & ~(0xf << 8)) | (0x3 << 8), gpfcon);
}static void led_on(void)
{writel(readl(gpfdat) & ~(0x1 << 2), gpfdat);
}static void led_off(void)
{writel(readl(gpfdat) | (0x1 << 2), gpfdat);
}static int fspad723_led_open(struct inode *inode, struct file *file)
{printk("led: device open\n");led_init();return 0;
}static int fspad723_led_close(struct inode *inode, struct file *file)
{printk("led: device close\n");led_recovery();return 0;
}static long fspad723_led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{printk("led: device ioctl\n");switch(cmd){case LED_ON:printk("led: ON\n");led_on();break;case LED_OFF:printk("led: OFF\n");led_off();break;default:printk("led: available command\n");break;}return 0;
}static struct file_operations fspad723_led_ops = {.owner         = THIS_MODULE,.open         = fspad723_led_open,.release     = fspad723_led_close,.unlocked_ioctl        = fspad723_led_ioctl
};static int led_setup_cdev(struct cdev *cdev, struct file_operations *fops)
{int result;dev_t devno = MKDEV(led_major, led_minor);cdev_init(cdev, fops);cdev->owner = THIS_MODULE;result = cdev_add(cdev, devno, 1);if(result){printk("led: cdev add failed\n");return result;}return 0;
}static ssize_t fspad723_led_enable_show(struct device *dev,struct device_attribute *attr, char *buf)
{return 0;
}static ssize_t fspad723_led_enable_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count)
{unsigned long data;int error;error = strict_strtoul(buf, 10, &data);printk("set value = %d\n", data);if (error)return error;if (data == 1) {printk("turn on\n");led_init();led_on();}else {printk("turn off\n");led_off();led_recovery();}return count;
}static DEVICE_ATTR(enable, 0777,fspad723_led_enable_show, fspad723_led_enable_store);static struct attribute *fspad723_led_attributes[] = {&dev_attr_enable.attr,NULL,
};static struct attribute_group fspad723_led_attributes_group = {.attrs = fspad723_led_attributes
};struct device *dev;static int __init fspad723_led_init(void)
{int result;dev_t devno = MKDEV(led_major, led_minor);result = register_chrdev_region(devno, 1, "fspad723_led");if(result){printk("led: unable to get major %d\n", led_major);return result;}result = led_setup_cdev(&led_cdev, &fspad723_led_ops);if(result) {printk("led: failed in cdev add.\n");unregister_chrdev_region(devno, 1);return result;}gpfcon = ioremap(FSPAD723_GPFCON, 4);if (gpfcon == NULL) {printk("led: failed in ioremap.\n");goto err1;}gpfdat = ioremap(FSPAD723_GPFDAT, 4);if (gpfcon == NULL) {printk("led: failed in ioremap.\n");goto err2;}led_class = class_create(THIS_MODULE, "farsight_led");if (IS_ERR(led_class)) {printk("led: failed in creating class.\n");goto err3;}dev = device_create(led_class, NULL, devno, NULL, "led");result = sysfs_create_group(&dev->kobj,&fspad723_led_attributes_group);if (result < 0){printk("led: sysfs_create_group err\n");goto err3;}printk("led: driver installed, with major %d!\n", led_major);return 0;
err3:iounmap(gpfdat);
err2:iounmap(gpfcon);
err1:cdev_del(&led_cdev);unregister_chrdev_region(devno, 1);return result;
}static void __exit fspad723_led_exit(void)
{dev_t devno = MKDEV(led_major, led_minor);sysfs_remove_group(&dev->kobj, &fspad723_led_attributes_group);device_destroy(led_class, devno);class_destroy(led_class);iounmap(gpfdat);iounmap(gpfcon);cdev_del(&led_cdev);unregister_chrdev_region(devno, 1);printk("led: driver uninstalled!\n");
}module_init(fspad723_led_init);
module_exit(fspad723_led_exit);/驱动Makefile//
#
# Makefile for the kernel character device drivers.
#
obj-m                += fspad723_led.o

总结:

1)      Manager是应用程序直接面对的接口

2)      Manager里维护对应的Service

3)      应用程序不能直接访问Service

为什么要这么设计??

使用Manager机制的好处是显而易见的:

l  Service是服务,服务是所有应用共享,不能属于某一个具体的进程,即:Android程序。

l  基于第一点,Android应用与Service在不同进程,它们之间必然要进行通信,要使用IPC,即:进程间通信,而框架的作用是让应用程序快速开发提供API,不可能让进程间通信的代码出现在Android应用中,这些所谓的后台操作不能让程序开发者感知到,即:隐藏通信手段与细节。

既然如此,那Google就给我们写一个Manager类,把共享服务隐藏起来,只暴露操作接口,操作细节,IPC细节统统后台去。

OK,理解了这个,可以看得出来,我们的之前写的LedHAL是如此的弱智,如此的没有规范,如此的不考虑框架设计,如此的不复用代码,如此…,太多了,没有办法,慢慢在后面再优化吧,差不多了,开始吧。

前面说了,使用Sensor服务要用SensorManager,让我们来看看一个简单应用的代码,再逐渐展开。

[java] view plain copy

  1. public class SensorAppDemoActivity extends Activity implementsSensorEventListener{
  2. private TextViewmTextView;
  3. /** Calledwhen the activity is first created. */
  4. @Override
  5. public voidonCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.main);
  8. mTextView= (TextView) findViewById(R.id.mTextView);
  9. // 得到SensorManager
  10. SensorManager sm = (SensorManager)this.getSystemService(SENSOR_SERVICE);
  11. // 获得指定类型的传感器对象
  12. SensortempSensor = sm.getDefaultSensor(Sensor.TYPE_TEMPERATURE);
  13. // 注册传感器对象的监听器
  14. sm.registerListener(this, tempSensor, SensorManager.SENSOR_DELAY_NORMAL);
  15. }
  16. @Override
  17. public voidonAccuracyChanged(Sensor sensor, int accuracy) {
  18. // TODOAuto-generated method stub
  19. // 未实现该方法
  20. }
  21. @Override
  22. public void onSensorChanged(SensorEvent event) {
  23. // TODOAuto-generated method stub
  24. mTextView.setText("Currenttemperature is :" + event.values[0]);
  25. }
  26. }

上面代码很简单,得到温度传感器对象后,注册传感器事件监听器,当数据变化时在Activity上的TextView上显示温度。

现在我们分析每个步骤框架的实现:

先来看Context.getSystemService(SENSOR_SERVICE)。

2)    获得Sensor系统服务

上面代码里getSystemService(String)是在当前Activity里直接调用的,说明它不是Activity的方法就是其父类的方法,按照继承关系向上查找:

@frameworks/base/core/Java/android/app/ContextImpl.java

[java] view plain copy

  1. @Override
  2. public Object getSystemService(String name) {
  3. ServiceFetcherfetcher = SYSTEM_SERVICE_MAP.get(name);
  4. returnfetcher == null ? null : fetcher.getService(this);
  5. }

这是什么?我恨Android新版本的代码,没有老版本的看着简单。先来看下SYSTEM_SERVICE_MAP是什么东西,看样子像映射关系:

[java] view plain copy

  1. private static final HashMap<String,ServiceFetcher> SYSTEM_SERVICE_MAP =
  2. newHashMap<String, ServiceFetcher>();

SYSTEM_SERVICE_MAP其实是一个哈希键值映射表,其Key为String类型,Value为ServiceFetcher类型,而我们获得服务时通过服务名来查找一个ServiceFetcher类型,并返回ServiceFetcher.getService()的结果作为SensorManager。

[java] view plain copy

  1. static class ServiceFetcher {
  2. intmContextCacheIndex = -1;
  3. publicObject getService(ContextImpl ctx) {
  4. // mServiceCache是ArrayList<Object>类型对象
  5. ArrayList<Object> cache =ctx.mServiceCache;
  6. Object service;
  7. synchronized (cache) {
  8. // 对于新创建的Activity mServiceCache里没有元素,所以size为0
  9. if (cache.size() == 0) {
  10. // Initialize the cache vector on first access.
  11. // At this point sNextPerContextServiceCacheIndex
  12. // is the number of potential services that are
  13. // cached per-Context.
  14. // sNextPerContextServiceCacheIndex为每个Android服务的索引值
  15. for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
  16. cache.add(null);  // 添加null对象
  17. }
  18. }else {     // size不为0的时候,即,之前已经调用过getSystemService
  19. service = cache.get(mContextCacheIndex);
  20. if (service != null) {
  21. return service;  // 直接拿到之前添加的对象返回
  22. }
  23. }
  24. service = createService(ctx);  // cache.size=0并且已经添加了一个null对象到cache里
  25. cache.set(mContextCacheIndex, service); // 设置新创建的服务添加到cache里
  26. return service;            // 返回该服务
  27. }
  28. }
  29. publicObject createService(ContextImpl ctx) { // 必须实现的方法
  30. thrownew RuntimeException("Not implemented");
  31. }
  32. }
  33. 通过分析代码可知,在ContextImpl类里维护了一个ArrayList<Object>对象,其里面保存着所有注册的Service对象,并且Service对象的获得和创建由ServiceFether来封装,该类就两个方法:createService和getService,而createService是未实现的方法。createSerive的实现在后面:
  34. private static int sNextPerContextServiceCacheIndex =0;
  35. private static void registerService(String serviceName, ServiceFetcher fetcher){
  36. if(!(fetcher instanceof StaticServiceFetcher)) {   //是否为StaticServiceFetcher的对象
  37. fetcher.mContextCacheIndex =sNextPerContextServiceCacheIndex++;
  38. }
  39. SYSTEM_SERVICE_MAP.put(serviceName, fetcher);  // 添加到SYSTEM_SERVICE_MAP键值表里
  40. }
  41. static {
  42. registerService(POWER_SERVICE, new ServiceFetcher() {
  43. public Object createService(ContextImpl ctx) {
  44. IBinder b = ServiceManager.getService(POWER_SERVICE);
  45. IPowerManager service = IPowerManager.Stub.asInterface(b);
  46. return new PowerManager(service, ctx.mMainThread.getHandler());
  47. }});
  48. registerService (SENSOR_SERVICE,new ServiceFetcher() {
  49. public Object createService(ContextImpl ctx) {
  50. return new SensorManager(ctx.mMainThread.getHandler().getLooper());
  51. }});
  52. }

好家伙,一下子在静态初始化语句块里通过私有方法registerService注册了30多个服务,其第二个参数为ServiceFetcher类型,每个注册的服务都是匿名内部类,都实现了createService方法,在createService方法里创建了SensorManager对象,该对象和Activity的Looper共享消息队列。

总结下吧:

用户程序调用getSystemService(SENSOR_SERVICE),其实现在ContextImpl.java中,在其实实现中从SYSTEM_SERVICE_MAP键值表查找与SENSOR_SERVICE键对应的对象ServiceFetcher,调用ServiceFetcher.getService方法得到SensorManager对象,而ContextImpl对象里还维护着一个ServiceCache,如果某个服务被get过一次,就会被记录在这个所谓缓存里,ServiceFetcher.getService先查找缓存里有没有Cache的Object,如果没有,则调用自己的createService方法创建这个Object,而createService没有实现,其在registerService注册服务时创建ServiceFetcher匿名内部类时实现,并且将注册的服务添加到SYSTEM_SERVICE_MAP中,在创建SensorManager对象时,它和Activity共享了一个Looper。

总而言之,言而总之,在getSystemService(SENSOR_SERVICE)里,创建了SensorManager对象并且和Activity共享Looper。

参考博客
led HAL简单设计案例分析
http://blog.csdn.net/mr_raptor/article/details/8082360

Android 灯光系统开发相关推荐

  1. Tiny4412——Android灯光系统

    CSDN仅用于增加百度收录权重,排版未优化,日常不维护.请访问:www.hceng.cn 查看.评论. 本博文对应地址: https://hceng.cn/2019/05/09/Tiny4412--A ...

  2. Android WiFi开发教程(三)——WiFi热点数据传输

    在上一篇文章中介绍了WiFi的搜索和连接,如果你还没阅读过,建议先阅读上一篇Android WiFi开发教程(二)--WiFi的搜索和连接.本篇接着简单介绍手机上如何通过WiFi热点进行数据传输. 跟 ...

  3. android ble 设备扫描程序,Android应用开发Android 7.0 BLE scan 问题:程序无错但扫描不到BLE设备...

    本文将带你了解Android应用开发Android 7.0  BLE scan 问题:程序无错但扫描不到BLE设备,希望本文对大家学Android有所帮助. < 最近在做毕设,需要几周内从头学起 ...

  4. Android Studio开发环境及第一个项目

    1. 在你的电脑上搭建Android平台开发环境. 2. 新建项目,实现以下基本内容: (1) 修改默认的APP的名称和图标(任意的,非默认的). (2) 显示个人信息,包括:照片.专业.姓名.学号等 ...

  5. Android UI开发第二十五篇——分享一篇自定义的 Action Bar

    Action Bar是android3.0以后才引入的,主要是替代3.0以前的menu和tittle bar.在3.0之前是不能使用Action Bar功能的.这里引入了自定义的Action Bar, ...

  6. [转]Android敏捷开发指南

    原文地址:http://www.apkbus.com/android-72730-1-1.html 本文紧密结合移动开发方法与技术,围绕Android平台的开发探讨提供更高质量移动产品的解决方案.作者 ...

  7. 《Android传感器开发与智能设备案例实战》——导读

    本节书摘来自异步社区<Android传感器开发与智能设备案例实战>一书中的目录,作者 朱元波,更多章节内容可以访问云栖社区"异步社区"公众号查看 目 录 前 言 第1章 ...

  8. IntelliJ IDEA 12详细开发教程(四) 搭建Android应用开发环境与Android项目创建

    今天我要给大家讲的是使用Intellij Idea开发Android应用开发.自我感觉使用Idea来进行Android开发要比在Eclipse下开发简单很多. (一)打开网站:http://devel ...

  9. 从Android界面开发谈起(转)

    原文地址:http://blog.csdn.net/nieweilin/article/details/5967815 这篇文章没有打算有一个很好的逻辑去介绍android的某个方面,全盘大致上就是我 ...

最新文章

  1. 深入理解Android中View
  2. 小程序存emoji表情 不改变数据库
  3. 处理字符串_5_字符串里过滤不需要的字符
  4. Embeded linux之移植boa
  5. 第十节:进一步扩展两种安全校验方式
  6. 全网呕血整理:关于YOLO v3原理分析
  7. 像Selenium爬网页一样爬手机App,可见即可爬——appium 教程(一)appium安装windows版
  8. 流媒体:RTMP 协议完全解析
  9. python地理数据处理环境搭建
  10. 关于python的英文参考文献_参考文献英文版
  11. ODC V3.2.0 新版本发布 | 着重用户体验,挑战权限管控业务场景
  12. 伪随机二进制序列(PRBS)
  13. 让QQ的文字漂亮起来
  14. 软工-点赞和取消点赞
  15. net-java-php-python-篮球新闻网站计算机毕业设计程序
  16. vue——数字加逗号分隔
  17. java实现在线预览word,excel,ppt文档
  18. 异或和校验(BCC)
  19. 给电脑重装系统的方法与重装过程中问题解决
  20. WLAN基本知识之无线基本概念

热门文章

  1. Ext2 中如何换行 布局?
  2. ElementUI文档中忽略的内容补充
  3. python day10
  4. Hive学习之路 (十九)Hive的数据倾斜
  5. layui + jfinal 实现上传下载
  6. 2017年10月21日23:43:02
  7. 过滤器显示一个路径下特定的文件
  8. 记一次远程协助的排错案例
  9. Quartus II调用modelsim ALTEA 的软件使用及问题
  10. Exchange Server 2010高可用性配置