Android传感器-开发指南
大部分Android平台的设备都带有多个传感器,使你能监视其方位和运动状态的变化。很多设备还有其它类型的传感器,让你能感知周围的环境条件,比如温度、压力、湿度和光线。你可以利用Android的传感器框架访问这些传感器,并获取原始的传感器数据。
传感器框架提供了丰富的类和接口,能帮助你完成很多与传感器有关的工作。比如,你可以用传感器框架来进行:
- 确定设备上可用的传感器
- 确定某个传感器的性能,比如量程、制造商、能耗、分辨率等。
- 读取传感器的原始数据并指定最小读取频率
- 注册及注销监听传感器变化的传感器事件侦听器
本文概述了Android平台可用的传感器,并简要介绍了传感器框架。
传感器介绍
Android传感器框架能让你访问很多类型的传感器。有些是基于硬件的,有些则是基于软件的。基于硬件的传感器是内置于手持或桌面设备中的物理部件。它们直接测量环境参数并发送数据,比如加速度、地磁强度、角速度等。基于软件的传感器则没有物理设备,虽然它们也是模仿硬件传感器。基于软件的传感器数据是来自一个或多个硬件传感器,有时也被称为虚拟传感器或合成传感器。直线加速度传感器和重力传感器就是基于软件的传感器。表1中列出了Android平台支持的全部传感器。
能够拥有全部类型传感器的Android设备非常少见。比如,大部分手持设备和桌面设备都带有加速度和磁力传感器,但带气压计和温度计的设备就少得多了。而且,在一台设备上某类传感器的数量也可以有多个。比如,一台设备可以带有两个重力传感器,每个传感器的量程各不相同。
表1. Android框架支持的传感器类型
传感器 |
类型 |
说明 |
常见用途 |
硬件 |
测量施于设备的物理三维方向上(x、y和z轴)的加速度,包括重力,单位为m/s 2。 |
运动检测(晃动、倾斜等) |
|
硬件 |
测量周围环境的温度,单位为摄氏度(°C)。参见下文。 |
监测气温 |
|
软件或硬件 |
测量施于设备的物理三维方向上(x、y和z轴)的重力加速度,单位为m/s 2 。 |
运动检测(晃动、倾斜等) |
|
硬件 |
测量设备围绕每个物理三维方向(x、y和z轴)的转动角速度,单位为rad/s 。 |
转动检测(旋转、转动等) |
|
硬件 |
测量周围环境的光照强度(照度),单位为lx。 |
控制屏幕亮度 |
|
软件或硬件 |
测量施于设备的物理三维方向上(x、y和z轴)的加速度,但不包括重力,单位为m/s 2。 |
监测某一维轴线上的加速度 |
|
硬件 |
测量周围物理三维方向(x、y和z轴)的地球磁场,单位为μT。 |
创建指南针 |
|
软件 |
测量围绕物理三维方向(x、y和z轴)的旋转角度。自API level 3开始,利用重力传感器和地磁传感器,你可以用 getRotationMatrix() 方法读取倾角矩阵和旋转矩阵。 |
检测设备的方位 |
|
硬件 |
测量周围大气压力,单位为hPa或mbar。 |
监测气压的变化 |
|
硬件 |
测量附近的物体与设备屏幕间的距离,单位为cm。此传感器的典型应用,是可以检测手持设备是否被人拿起来并靠近耳朵。 |
通话时确定电话的位置 |
|
硬件 |
测量周围环境的相对湿度,单位为百分比(%)。 |
监测结露点、绝对湿度和相对湿度。 |
|
软件或硬件 |
根据设备旋转向量的三个参数测量设备的方向。 |
运动检测和转动检测 |
|
硬件 |
测量设备的温度,单位是摄氏度(°C)。这个传感器的实现因设备的差异而各不相同,并自API Level 14开始由 TYPE_AMBIENT_TEMPERATURE 代替。 |
监测温度 |
传感器框架
通过使用Android传感器框架,你可以访问这些传感器并读取传感器的原始数据。Android传感器框架是 android.hardware 包的一部分,包含了以下类和接口:
你可以用这个类来创建传感器设备的一个实例。这个类提供了多个方法,用于访问及获取传感器列表、注册及注销传感器事件侦听器、读取方位信息等。该类还提供了众多的传感器常量,用于报告传感器精度、设置数据采样率和校准传感器。
你可以用这个类来创建某个传感器的实例。该类提供了很多方法,使你能确定传感器的性能。
系统用该类来创建一个传感器事件对象,用于提供传感器事件的相关信息。这些信息包括:传感器原始数据、生成本事件的传感器类型、数据的精度、事件的时间戳。
你可以用该类来创建两个回调方法,用于传感器数值改变时或传感器精度变化时接收通知(传感器事件)。
在典型的应用中,你一般会用这些传感器API完成两个基本的任务:
- 识别传感器及其性能
如果你的应用有部分功能依赖于某些传感器,则在运行时识别传感器的类型和性能是非常有用的。比如,你也许需要识别出当前设备上所有的传感器,并把那些所需传感器不可用的功能禁用掉。同理,你可能还想识别出所有的某类型传感器,以便应用程序能够选用其中性能最佳的那个。
- 监听传感器事件
监听传感器事件是获取传感器原始数据的途径。每当传感器检测到其测量的参数发生变化时,传感器事件就会被触发。传感器事件会向你提供四块信息:触发事件的传感器名称、事件的时间戳、事件的精度、触发事件的传感器原始数据。
传感器的可用性
传感器的可用性不仅会因设备不同而各不相同,也会因Android的版本而不一样。这是因为Android传感器的引入跨越了多个版本。比如,很多传感器是自Android 1.5 (API Level 3) 开始引入的,但其中有些并未实现而直至Android 2.3 (API Level 9) 才可用。同样,一部分传感器是自Android 2.3 (API Level 9) 和Android 4.0 (API Level 14) 才引入的。有两个传感器已过时,被更新更好的传感器所替代。
表 2 汇总了每个版本可用的传感器。由于涉及传感器变动的版本只有四个,这里只列出了这四个版本。标明过时的传感器在后续的版本中仍是可用的(当然设备要提供此传感器),这也符合Android向下兼容性原则。
表2. 各版本可用的传感器
传感器 |
Android 4.0 |
Android 2.3 |
Android 2.2 |
Android 1.5 |
是 |
是 |
是 |
是 |
|
是 |
n/a |
n/a |
n/a |
|
是 |
是 |
n/a |
n/a |
|
是 |
是 |
n/a 1 |
n/a 1 |
|
是 |
是 |
是 |
是 |
|
是 |
是 |
n/a |
n/a |
|
是 |
是 |
是 |
是 |
|
是 2 |
是 2 |
是 2 |
是 |
|
是 |
是 |
n/a 1 |
n/a 1 |
|
是 |
是 |
是 |
是 |
|
是 |
n/a |
n/a |
n/a |
|
是 |
是 |
n/a |
n/a |
|
是 2 |
是 |
是 |
是 |
1 此传感器类型自 Android 1.5 (API Level 3) 起引入,但直至 Android 2.3 (API Level 9) 才可用。
2 此传感器可用,但已过时。
识别传感器及其性能
Android的传感器框架提供了众多的方法,使你很容易就能在运行时检测出当前设备可用的传感器。API也提供了很多检测每个传感器性能的方法,比如量程、分辨率、能耗。
要识别设备上的传感器,你首先需要获取一个传感器设备的引用。你可以通过调用 getSystemService() ,并传入 SENSOR_SERVICE 参数,来创建一个 SensorManager 。例如:
private SensorManager mSensorManager;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
然后,你可以用 TYPE_ALL 参数调用 getSensorList() 方法,以获取一个包含设备上所有传感器的列表。例如:
List<Sensor> deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
如果你需要列出某指定类型的所有传感器,你可以用其它常量来代替 TYPE_ALL ,比如 TYPE_GYROSCOPE 、 TYPE_LINEAR_ACCELERATION 、 TYPE_GRAVITY 。
你还可以用某类型常量作为参数的 getDefaultSensor() 来检测设备上是否存在该类型的传感器。如果设备上给定类型的传感器不止一个,则其中一个传感器必须指定为缺省传感器。如果给定类型的缺省传感器不存在,则该方法返回null,表示设备没有该类型的传感器。例如,以下代码检查设备上是否存在磁力计:
private SensorManager mSensorManager;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null){
// 成功!磁力计存在。
}
else {
// 失败|没有磁力计。
}
注意: Android并未要求制造商在其Android平台的设备上安装任何类型的传感器,因此设备上可能会存在各种各样的传感器。
除了列出设备上的传感器清单外,你还可以用 Sensor 类的公共方法来确定每个传感器的性能和参数。如果你的应用需要根据传感器及其性能的差异作出不同的表现,这就非常有用。比如,你可以用 getResolution() 和 getMaximumRange() 方法来获取传感器的分辨率及最大量程。你还可以用 getPower() 方法来获取传感器的能耗。
如果你需要根据传感器的制造商或版本来对应用进行优化,有两个公共方法特别有用。比如,如果你的应用需要监控倾斜或者晃动之类的用户手势,则针对较新的带某厂商重力传感器的设备,你可以建立一套数据过滤规则和优化措施,而针对没有重力传感器只有加速度计的设备你可以建立另一套数据过滤器和优化措施。以下例程展示了如何用 getVendor() 和 getVersion() 来完成这类工作的。在此例中,我们将先查找Google公司出品的版本为3的重力传感器。如果设备上不存在这类传感器,我们再尝试使用加速计。
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) != null){
List<Sensor> gravSensors = mSensorManager.getSensorList(Sensor.TYPE_GRAVITY);
for(int i=0; i<gravSensors.size(); i++) {
if ((gravSensors.get(i).getVendor().contains("Google Inc.")) &&
(gravSensors.get(i).getVersion() == 3)){
// 使用版本3的重力传感器
mSensor = gravSensors.get(i);
}
}
}
else{
// 使用加速计
if (mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null){
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
else{
// 抱歉,设备上不存在加速计,你无法玩这个游戏。
}
}
其它较有用的是 getMinDelay() 方法,用于返回传感器采集数据的最小时间间隔(微秒)。任何 getMinDelay() 返回非零值的传感器都是流式传感器(streamingsensor)。流式传感器以一定的时间间隔有规律地测量数据,自Android 2.3 (API Level 9) 开始引入。如果调用 getMinDelay() 时返回零,这就表示该传感器不是流式传感器,只有所监测的参数发生变化时它才会报送数据。
getMinDelay() 方法能让你确定传感器的最大采样频率,因此它是非常有用的。如果你的应用中某项功能需要很高的数据采样率或者要用到流式传感器,你就可以用此方法先确认传感器是否符合要求,然后再据此来启用或禁用相关的功能。
警告: 传感器框架并不一定按照传感器的最大数据采样率来向你的应用发送数据。传感器框架是通过传感器事件来报送数据的,而很多因素会影响到应用程序对传感器事件的接收频率。详情请参阅 监听传感器事件 。
监听传感器事件
要监控传感器的原始数据,你需要实现 SensorEventListener 接口的 onAccuracyChanged() 和 onSensorChanged() 回调方法。只要发生以下事件,Android系统就会调用这两个方法:
- 传感器精度发生变化
在这种情况下,系统会调用 onAccuracyChanged() 方法,并传给你一个发生变化的 Sensor 对象的引用和新的传感器精度值。精度用以下四种状态常量之一来表示: SENSOR_STATUS_ACCURACY_LOW、 SENSOR_STATUS_ACCURACY_MEDIUM、 SENSOR_STATUS_ACCURACY_HIGH、和 SENSOR_STATUS_UNRELIABLE。
- 传感器报送一个新数据
这种情况下,系统会调用 onSensorChanged() 方法,并传给你一个 SensorEvent 对象。 SensorEvent对象中包含了新数据的相关信息,包括:数据精度、生成数据的传感器、生成数据的时间戳、传感器采到的新数据。
以下代码展示了如何用 onSensorChanged() 方法来监控光线传感器传回的数据,并把原始数据显示在一个由main.xml文件定义为sensor_data的 TextView 中。
public class SensorActivity extends Activity implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor mLight;
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// 如果传感器精度发生变化,可以在这里完成些工作。
}
@Override
public final void onSensorChanged(SensorEvent event) {
// 光线传感器返回单个值。
// 很多传感器会返回3个值,代表每个坐标轴上的数值。
float lux = event.values[0];
// 利用此值完成一些工作
}
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, mLight, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
}
在此例中,调用 registerListener() 时指定了缺省的数据延时 ( SENSOR_DELAY_NORMAL)。数据延时(或采样率)控制着由 onSensorChanged() 发送给应用的传感器事件的触发间隔。缺省的数据延迟是200,000微秒,适于监测典型的屏幕方向变动。你可以把数据延时指定为其它值,比如 SENSOR_DELAY_GAME (20,000微秒)、 SENSOR_DELAY_UI (60,000微秒)或 SENSOR_DELAY_FASTEST (0微秒)。Android3.0 (API Level 11) 开始,你还可以直接指定延时值(微秒数)。
你指定的延时只是一个建议值。Android系统和其它应用可以修改这个值。最佳方案是,你应该指定你能承受的最大延时,因为系统一般会采用一个比设定稍小一点的值(也就是说,你应该选择应用所需的最慢采样率)。采用更大的延时能够降低处理器的负载并减少耗电量。
传感器框架发送传感器事件的实际频率,是没有现成的公共方法来判断的,不过,你可以根据多个传感器事件的时间戳来计算出采样率。一旦采样率(延时)设置完成,你就不应该改变它。如果由于某种原因需要修改延时,那你就必须注销并重新注册传感器侦听器。
还有一点非常重要,请注意上例中使用了 onResume() 和 onPause() 回调方法来注册和注销传感器事件侦听器。最佳方案就是,你应该保证在不用时及时关闭传感器,特别当你的activity被暂停时。不然,因为某些传感器的能耗很大,会快速消耗电池电量,可能会在几个小时内将电池耗尽。当屏幕关闭时,系统会自动禁用所有传感器。
处理各种传感器配置
Android并没有设定标准的传感器配置,这意味着设备制造商可能会把所有要装入设备的传感器配置都放进Android平台的设备中。这样,设备就可能包括了各种传感器的大量配置信息。比如,MotorolaXoom带有压力传感器,而SamsungNexus S就没有。同理,Xoom 和 NexusS 都带有陀螺仪,但是HTC Nexus One 却没有。如果你的应用依赖于特定类型的传感器,你不得不确认设备是否提供了该传感器,以保证你的应用能成功运行。你有两种方式来确认传感器的存在:
- 在运行时检测传感器并酌情启用或禁用应用程序的相应功能
- 使用Android Market过滤器来限定目标设备必须带有特定传感器
这两种方式将在下节介绍。
在运行时检测传感器
如果你的应用程序用到了特定类型的传感器,不过并不是必须使用它,那么你可以在运行时利用传感器框架来检测它,并酌情启用或禁用相应功能。比如,一个导航应用也许会用到温度、压力、GPS和地磁传感器来显示温度、气压、位置和南北方位。如果设备不提供压力传感器,你可以在运行时用传感器框架来检测压力传感器是否存在,然后在应用界面上关闭气压的显示。例如,以下就是检测设备是否提供压力传感器的代码:
private SensorManager mSensorManager;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) != null){
// Success! There's a pressure sensor.
}
else {
// 失败!传感器不存在。
}
用Android Market 过滤器来限定目标设备必须带有指定的传感器配置
如果你要在 Android Market 上发布应用,你可以用manifest 文件中的 <uses-feature> 元素把不提供所需传感器的设备过滤掉。manifest 文件中的<uses-feature>元素有很多硬件描述符,利用它们你可以根据传感器存在与否来对应用进行过滤。可列出的传感器包括:加速计、气压计、罗盘(地磁)、陀螺仪、光线和邻近距离。以下是滤除无加速计的manifest 样例:
<uses-feature android:name="android.hardware.sensor.accelerometer"
android:required="true" />
如果你把这个元素和描述符加入你的 manifest 中,则只有设备上带有加速计的用户才能在Android Market 上看到你的应用。
仅当应用程序完全依赖于某指定传感器时,你才能把描述符设置 android:required="true"。如果你的应用中只有某些功能用到了传感器,而没有传感器的话仍然能正常运行,那么你可以把传感器列在 <uses-feature>中,但应设置 android:required="false" 。这样可以确保没有此传感器的设备也能安装你的应用。这也是项目管理实践中的最佳方案,有助于你时刻了解应用所需要的硬件特性。请记住,如果你的应用用到了某个传感器,但没有此传感器也能运行,那你就必须在运行时检测传感器,并酌情禁用或启用相应功能。
传感器的坐标系
通常,传感器框架使用标准的三维坐标系来表示数据。对大多数传感器而言,该坐标系是以设备保持默认方向时的屏幕为参照物来定义的(参见图1)。当设备保持默认方向时,X轴表示从左到右的水平方向,Y轴表示自下而上的垂直方向,Z轴表示相对屏幕表面由内而外的方向。在这一坐标系中,屏幕背后的坐标用Z轴的负值表示。以下传感器会用到该坐标系:
要理解这个坐标系,最重要的一点就是,屏幕方向变化时坐标轴并不移动——也就是说,设备移动时传感器的坐标系永不改变。这与 OpenGL坐标系类似。
理解坐标系的另一个要点,你的应用不得假定设备的初始(默认)方向是竖直的。很多桌面设备的初始方向是横向放置的。传感器的坐标系总是以设备的初始方向为基准的。
最后,如果你的应用需要把传感器数据与屏幕显示关联,则你要用 getRotation() 方法来确定屏幕的转动方向,然后用 remapCoordinateSystem() 方法把传感器坐标映射为屏幕坐标。即使你的manifest文件已经指定为仅支持纵向显示,你仍需要这么做。
关于传感器坐标系的更多信息,包括如何处理屏幕旋转的相关信息,请参阅 One Screen Turn Deserves Another
注意: 某些传感器和方法的坐标系使用了地球参照系(而非设备参照系)。这些传感器和方法返回的数据表示了相对大地坐标而言的设备运动和地理位置。详情请参阅 getOrientation() 方法、 getRotationMatrix() 方法、 方向传感器 和 旋转矢量传感器。
访问和使用传感器的最佳实现方案
当你设计传感器相关的代码时,请确保遵守本节所列出的规范。这些规范作为最佳实现方案进行推荐,适用于需要使用传感器框架来访问传感器和读取传感器数据的任何人员。
注销传感器侦听器
当不再使用传感器或相关activity暂停时,确保及时注销传感器侦听器。如果传感器侦听器已注册而相关activity被暂停,传感器仍会继续测量数据并消耗电池资源,除非你注销了传感器。以下代码展示了如何利用 onPause() 方法来注销侦听器:
private SensorManager mSensorManager;
...
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
详情请参阅 unregisterListener(SensorEventListener)。
不要在模拟器上测试你的代码
目前无法在模拟器上测试传感器相关的代码,因为模拟器不能模拟传感器。你必须在物理设备上测试传感器相关代码。不过,你可以利用传感器的模拟器来模拟传感器的输出。
不要阻塞onSensorChanged() 方法
传感器数据以很高的频率在发生变化,这意味着系统可能会非常频繁地调用 onSensorChanged(SensorEvent)方法。最佳实现方案是,在 onSensorChanged(SensorEvent) 方法中你应该尽可能少干些事情,以防止阻塞。如果你的应用需要对传感器数据进行过滤或剔除操作,则应该在 onSensorChanged(SensorEvent) 方法之外进行。
避免使用过时的方法或传感器类型
有几个方法和常量已经过时了。特别是 TYPE_ORIENTATION 传感器类型已经过时。要获取方位数据,你应该换用 getOrientation() 方法。同样, TYPE_TEMPERATURE 传感器类型也已过时。在Android 4.0 的设备上,你应该用 TYPE_AMBIENT_TEMPERATURE 传感器类型来代替。
在使用前先验证传感器
在试图读取数据前,请确保先验证一下传感器是否存在。不要因为传感器很常用,就简单地假定它会存在。制造商并不需要在他们的设备上提供任何传感器。
谨慎选择传感器延时
当利用 registerListener() 方法注册传感器时,请确保为你的应用或使用场景选择了合适的发送频率。传感器能够以很高的频率发送数据。 请保证系统有能力发送其它数据,不要无谓浪费系统资源和消耗电池电量。
参考:http://developer.android.com/guide/topics/sensors/sensors_overview.html