大多数Android设备有内置的传感器,来测量运动,方向和各种环境条件。这些传感器能提供高精度和准确度的原始数据,如果你想监控设备三维运动或者位置,或者你想监控设备周围的环境变化,是非常有用的。例如,游戏可能跟踪设备重力传感器的数据,来推断复杂的用户首饰和动作,例如倾斜,震动,旋转,或者振幅。同样的,天气应用可能使用设备的温度传感器和湿度传感器的数据来计算和报告结露点,或者旅行应用可能使用磁场传感器和加速度传感器来报告一个指南针方位。
Android平台支持三大类的传感器:
位移传感器
这些传感器测量沿三个轴线测量加速度和旋转。这类包含家加速度,重力传感器,陀螺仪,和矢量传感器。
环境传感器
这些传感器测量各种环境参数,例如周围的空气温度和压力,光线,和湿度。这类包含气压,光线,和温度传感器。
位置传感器
这些传感器测量设备的物理位置。这类包含方向和磁力传感器。
你能访问设备上可用的传感器,并通过使用Android传感器框架获取原始传感器数据。传感器框架提供了一些类和接口,来帮助你执行各种传感器相关的任务。例如,你能使用传感器框架做如下事情:
确定什么传感器在设备上有效。
确定当个传感器的功能,例如它的最大射程,厂商,电力需求,和分辨率。
获取原始数据,并定义你获取传感器数据的最小速率。
注册和注销传感器事件监听,来监听传感器改变。
这个主题提供了在Android平台上的传感器的概览。它也介绍了传感器框架。
传感器介绍
———————————————————————————————————————————————————————————
Android传感器框架让你访问许多类型的传感器。这些传感器的一些事基于硬件的,一些是基于软件的。基于硬件的传感器是内嵌到手机或者平板中的物理元件,它们通过直接测量指定的环境属性来得到它们的数据,例如加速度,磁场强度,或者角度变化。基于软件的传感器不是物理设备,尽管它们模仿基于硬件的传感器。基于软件的传感器从一个或更多基于硬件的传感器获取它们的数据,并且有时候被称为虚拟传感器或者合成传感器。线性加速度传感器和重力传感器是基于软件传感器的例子。表1总结了Android平台支持的传感器。
很少Android设备有所有类型的传感器。例如,大部分手机和平板有一个加速计和磁场计,但是很少的设备拥有气压或者温度传感器。并且,一个设备可以拥有一个类型不止一个的传感器。例如,设备能有两个重力传感器,每个有不同的范围。
表1.Android平台支持的传感器类型
传感器 |
类型 |
描述 |
常见用法 |
TYPE_ACCELEROMETER |
硬件 |
以m/s2测量它设备所有三个物理轴线方向(x,y,和z)加速度,包括重力。 |
运动检测(震动,倾角等)。 |
TYPE_AMBIENT_TEMPERATURE |
硬件 |
以摄氏度测量周围空间的温度,参阅下面的注释。 |
检测空气温度。 |
TYPE_GRAVITY |
软件或硬件 |
以m/s2测量重力, |
运动检测(震动,倾角等)。 |
TYPE_GYROSCOPE |
硬件 |
以rad/s测量设备三个物理轴线方向(x,y,和z)。旋转速度。 |
旋转检测(旋转,转动等)。 |
TYPE_LIGHT |
硬件 |
以lx测量周围的光线级别。 |
控制屏幕的亮度。 |
TYPE_LINEAR_ACCELERATION |
软件或硬件 |
以m/s2测量设备所有的三个物理轴线方向(x,y,和z)的加速度,包含包含重力。 |
检测沿着一个轴向的加速度。 |
TYPE_MAGNETIC_FIELD |
硬件 |
以uT测量周围的三个物理轴线方向的磁场。 |
创建一个罗盘。 |
TYPE_ORIENTATION |
软件 |
测量设备所有三个物理轴线方向(x,y和x)的旋转角度。当使用Level 3的API的时候,你能通过使用重力传感器和磁场传感器,结合getRotatinMatrix()方法,获取设备的倾斜矩阵和旋转矩阵。 |
检测设备的位置。 |
TYPE_PRESSURE |
硬件 |
以hPa和mbar测量周围空气气压。 |
检测空气气压的改变。 |
TYPE_PROXIMITY |
硬件 |
|
手机在通话时的位置。 |
TYPE_RELATIVE_HUMIDITY |
硬件 |
一个百分比测量周围相对湿度。 |
检测结露点,绝对,和相对湿度。 |
TYPE_ROTATION_VECTOR |
软件或硬件 |
通过提供设备的三个旋转矢量测量设备方向。 |
检测运动和检测旋转。 |
TYPE_TEMPERATURE |
硬件 |
以摄氏度测量设备的温度。这个传感器在不同设备实现不同,并且这个传感器在API Level 14使用TYPE_AMBIENT_TEMPERATURE替代。 |
检测温度。 |
传感器框架
你能访问这些传感器,并通过使用Android传感器框架获取原始数据。Android传感器框架式android.hardware包的一部分,包含下面的类和接口:
SensorManager
你能使用这个类来创建一个传感器服务的实例。这个类提供了各种方法类访问和列举传感器,注册和注销传感器事件监听,并获取相应的信息。这个类也提供了几个传感器的常量,用户报告传感器的精确度,设置数据获取速率,和校准传感器。
Sensor
你能使用这个类类创建一个指定传感器的实例。这个类提供了各种方法让你确定传感器的功能。
SensorEvent
系统使用这个类来创建一个传感器对象,它提供了关于传感器事件的信息。一个传感器事件包含一下信息:原始传感器数据,这类传感器产生的事件,数据的准确性,和事件的时间戳。
SensorEventListener
你能使用这个接口来创建两个回掉方法,当传感器的值改变或者当传感器的精度改变的时候,它接受通知(传感器事件)。
在一个典型的应用程序中,你使用这些传感器相关的API来执行两个基本任务:
识别传感器和传感器的性能
如果你的应用程序有功能依赖指定的类型的传感器和功能,在运行时识别传感器和传感器的功能是非常有用的。例如,你可能想识别设备上可用的所有传感器,和禁用所有依赖不存在的传感器的应用程序功能。同样,你可能想识别一个指定类型的所有传感器,所以你能选择这个传感器来为你的应用程序实现最佳性能。
检测传感器事件
检测传感器事件是你如何获取原始传感器数据。传感器事件每次发生的时候,传感器检测到它测量的参数的改变。传感器事件给你提供了四个方面的信息:触发这个事件的传感器的名称,事件的时间戳,事件的精准度,和触发事件的原始传感器数据。
传感器的可用性
由于传感器可用性设备和设备之间不同,不同的Android版本也不同。这是因为Android的传感器在过去的几个版本都被介绍了。例如,一些传感器在Android1.5(API Level 3)中被介绍,但是一些直到Android2.3(API Level 9)也没有被实现可用。同样的,多个传感器在Android2.3(API Level 9)和Anroid4.0(API Level 14)被介绍。两个传感器被弃用,并且被新的,更好的替代。
表2概括了每个传感器逐个平台分析的可用性。仅仅四个平台被列出是因为这些平台包含传感器的变化。被列出的传感器被弃用仍可在后续的平台可用(提供的传感器在设备上存在),这符合Android向前兼容的策略。
表2.传感器平台的可用性
传感器 |
Android4.0(API Level 14) |
Android2.3(API Level9) |
Android2.2(API Level 8) |
Android1.5(API Level 3) |
TYPE_ACCELEROMETER |
Yes |
Yes |
Yes |
Yes |
TYPE_AMBIENT_TEMPERATURE |
Yes |
n/a |
n/a |
n/a |
TYPE_GRAVITY |
Yes |
Yes |
n/a |
n/a |
TYPE_GYROSCOPE |
Yes |
Yes |
n/a1 |
n/a1 |
TYPE_LIGHT |
Yes |
Yes |
Yes |
Yes |
TYPE_LINEAR_ACCELERATION |
Yes |
Yes |
n/a |
n/a |
TYPE_MAGNETIC_FIELD |
Yes |
Yes |
Yes |
Yes |
TYPE_ORIENTATION |
Yes2 |
Yes2 |
Yes2 |
Yes |
TYPE_PRESSURE |
Yes |
Yes |
n/a1 |
n/a1 |
TYPE_PROXIMITY |
Yes |
Yes |
Yes |
Yes |
TYPE_RELATIVE_HUMIDITY |
Yes |
n/a |
n/a |
n/a |
TYPE_ROTATION_VECTOR |
Yes |
Yes |
n/a |
n/a |
TYPE_TEMPERATURE |
Yes2 |
Yes |
Yes |
Yes |
1这个类型的传感器在Android1.5(API Level 3)被添加,但是直到Android2.3(API Level 9)也不可用。
2这个传感器是可用的,但是它被弃用。
识别传感器和传感器的性能
———————————————————————————————————————————————————————————
Android传感器框架提供了许多方法,它使你的运行时确定设备上有哪些传感器变得更容易。这个API也提供了方法,让你确定传感器的性能,例如它的大小范围,它的分辨率,和它要求的电力。
为了识别在设备上的传感器,你首先需要获取传感器服务的索引。为此,你通过调用getSystemService()方法并传递SENSOR_SERVICE参数,创建SensorManager类的一个实例。例如:
- private SensorManager mSensorManager;
- ...
- mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
- List<Sensor> deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
你也可以通过调用getDefaultSensor()方法并传递指定传感器的类型常量,来确定在设备上一个指定类型的传感器是否存在。如果设备上有超过一个指定类型的传感器,一个传感器必须被指定为默认的传感器。如果一个指定类型的传感器默认不存在,这个方法返回null,这意味着设备没有这个类型的传感器。例如,下面的代码用来检测在设备上是否有一个磁场计:
- private SensorManager mSensorManager;
- ...
- mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
- if (mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null){
- // Success! There's a magnetometer.
- }
- else {
- // Failure! No magnetometer.
- }
除了列出设备上的传感器之外,你能使用Sensor类的公共方法来检测个传感器的性能和属性。如果你想你的应用程序,基于设备上可用的不同的传感器或者不同的传感器性能,有不同的行为,这是非常有用的。例如,你可以使用getResolution()和getMaximumRange()方法类获取传感器的测量的分辨率和大小范围。你也能使用getPower()方法类获取传感器的电力需求。
如果你想针对不同厂商的传感器或者不同版本的传感器,优化你的应用程序,这两个公共方法非常有用。例如,如果你的应用程序需要检测用户的手势,例如震动和倾斜,你应该创建一个数据过滤规则集合,针对最新的有指定厂商的重力传感器的设备优化,和其它的数据过滤规则和针对没有重力传感器和仅有一个加速度计的设备优化。下面的代码例子向你展示了你如何能使用getVendor()和getVersion()方法来实现它。在这个例子中,我们查找一个Google Inc为厂商和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)){
- // Use the version 3 gravity sensor.
- mSensor = gravSensors.get(i);
- }
- }
- }
- else{
- // Use the accelerometer.
- if (mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null){
- mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- }
- else{
- // Sorry, there are no accelerometers on your device.
- // You can't play this game.
- }
- }
getMinDelay()方法是非常有用的,因为它让你确定了传感器获取数据最小速率。如果在你应用程序中的某一个功能需要高数据获取率或者一个流式传感器,你能使用这个方法类确定是否这个传感器满足这些要求,然后相应的启动或禁止你的应用程序的相关功能。
当心:传感器的最大数据获取率并不一定是这个传感器框架给你的应用程序发送传感器数据的速率。传感器框架通过传感器事件报告数据,并且多个因素影响你的应用程序获取传感器事件的速率。更多信息查阅Monitoring Sensor Events。
监测传感器事件
—————————————————————————————————————————————————————————————
为了监测原始数据你需要实现两个通过SensorEventListener接口定义的回掉方法:
传感器精度的变化
在这种情况下系统调用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) {
- // Do something here if sensor accuracy changes.
- }
- @Override
- public final void onSensorChanged(SensorEvent event) {
- // The light sensor returns a single value.
- // Many sensors return 3 values, one for each axis.
- float lux = event.values[0];
- // Do something with this sensor value.
- }
- @Override
- protected void onResume() {
- super.onResume();
- mSensorManager.registerListener(this, mLight, SensorManager.SENSOR_DELAY_NORMAL);
- }
- @Override
- protected void onPause() {
- super.onPause();
- mSensorManager.unregisterListener(this);
- }
- }
你指定的延迟仅仅是一个建议延迟。Android系统和其它应用系统可以改变这个延迟。最好的方法,你应该指定你可以指定的最大延迟,因为系统通常会使用一个比你指定的小的延迟(即,你应该选择最慢的采样率,但是仍然满足你的应用程序的需求)。使用更大的延迟在处理器上强加更小的负载,因此耗能更低。
没有公共的方法来测定传感器框架向你的应用程序发送传感器事件的速率;然而,你可以使用时间戳,它和每个传感器事件在几个事件的基础上计算采样速率相关。一旦你设置了它,你不能改变采样速率(延迟)。如果由于一些原因,你必须改变延迟,你将不得不注销和注册传感器监听器。
同样重要的是要注意这个例子使用onResume()和onPause()回调方法来注册和注销传感器事件监听器。最为一项最好方法,你应该总是在你不需要的时候禁用传感器,尤其是当你的Activity被Pause的时候。没有这样做可能在短短几个小时之内耗尽电池,因为一些传感器有很大的功率要求,并且会很快用完电池。当屏幕关闭的时候系统将会自动禁用传感器。
处理不同的传感器配置
————————————————————————————————————————————————————————————
Android没有为设备指定一个标准的传感器配置,这意味着设备厂商可以将任何它们想要的传感器配置安装到他们的的Android设备。造成一个结果,设备包含了在大范围配置的各种传感器。例如,Motorola Xoom有一个压力传感器,但是Samsung Nexus S没有。同样,Xoom和Nexus S有陀螺仪,但是HTC Nexus One没有。如果你的应用程序依赖于一个指定类型的传感器看,你必须确保这个传感器在设备上存在,以至于你的应用程序能成功运行。你有两个选择来确保一个给定的传感器在设备上存在:
在运行时检测传感器,并酌情启动或禁用应用程序的功能。
使用Google Play过滤器来规定制定传感器配置的设备。
每个选择都在下面的章节被讨论。
在运行时检测传感器
如果你的应用程序使用一个指定的传感器,但是不依赖它,你能使用传感器框架在运行时检测传感器,然后酌情禁用和启动应用程序功能。例如,一个导航引用程序可能使用温度传感器,压力传感器,GPS传感器,和磁场传感器来显示温度,气压,位置和罗盘方位。如果设备没有一个压力传感器,你能使用传感器框架在运行时检测压力传感器的存在,然后你的应用程序显示压力的UI的部分。例如,下面的代码检查设备上是否有一个压力传感器:
- private SensorManager mSensorManager;
- ...
- mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
- if (mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) != null){
- // Success! There's a pressure sensor.
- }
- else {
- // Failure! No pressure sensor.
- }
如果你在Google Play中发布你的应用程序,你能在你的清单文件中使用<uses-feature>元素来过滤没有你的应用程序相应的传感器配置的设备。<uses-feature>元素有多个硬件描述符,让你基于是否存在指定的传感器来过滤应用程序。你能列举的传感器包括:加速度,气压,罗盘(磁场),陀螺仪,光线,和趋近。下面是一个示例清单实例,来过滤没有加速度传感器的应用程序。
- <uses-feature android:name="android.hardware.sensor.accelerometer"
- android:required="true" />
仅仅当你的应用程序彻底依赖一个特定的传感器的时候,你应该设置这个描述符为android:required="true"。如果你的应用程序的一些功能使用一个传感器,但是没有传感器仍然可以运行,你应该在<uses-feature>元素中列出这个传感器,但是设置这个描述符为android:required="false"。这个帮助确保设备能安装你的应用,即使它们没有这个特定的传感器。这也是一个项目管理的最佳实践,帮组你跟踪你的应用程序使用的特性。记住,如果你的应用程序使用一个特定的传感器,但是没有它仍然可以运行,那么你应该在运行时检测这个传感器,并且酌情启动或禁用应用程序的功能。
传感器坐标系
—————————————————————————————————————————————————————————
通常,传感器框架使用一个标准的3维坐标系来表达数据值。对于大多数传感器,当设备放置默认的方向(看图1)的时候,坐标系被定义和设备的屏幕相关。当设备放置为它默认的方向,X轴是水平并指向右边,Y轴是竖直并指向上方,并且Z轴指向屏幕面的外侧。在这个系统,坐标系统有负的Z值。这个坐标系被用于下面的传感器:
加速度传感器
重力传感器
陀螺仪传感器
线性加速度传感器
磁场传感器
图1.传感器API使用的坐标系系统(相对于一个设备)
理解这个坐标表系的最重要的一点是,当设备屏幕的方向改变的时候,轴不改变—就是说,传感器的坐标系当设备移动的时候从来都不会改变。这个和OpenGL坐标系是相同的。
理解的另一点是你的应用程序不能假定设备的自然(默认)方向是竖屏。对于许多平板设备的自然方向是横屏。并且传感器的坐标系总是急于设备的自然方向。
最终,如果你的应用程序匹配传感器数据到屏幕显示,你需要使用getRotation()方法来确定屏幕的旋转,然后使用remapCoordinateSystem()方法来映射传感器坐标系到屏幕坐标系。即使你的清单文件指定了仅仅竖屏幕显示你也需要这样做。
更多关于传感器坐标系的信息,包含如何处理屏幕旋转的信息,查阅One Screen Turn Deserves Another。
注意:一些传感器和方法使用的坐标系是相对于真实世界的参照(相对于设备的参考框架)。这些传感器和方法返回数据代表设备的运动或者设备相对于地球的位置。更多信息,查阅getOrientation()方法,getRotationMatrix()方法,Orientation Sensor,和Rotation Vector Sensor。
访问和使用传感器的最佳实践
—————————————————————————————————————————————————————————————
当你设计你的传感器实现的时候,确保遵守这个章节下面讨论的准则。这些准则是为任何使用传感器框架访问并获取传感器数据的人,推荐的最佳实践。
注销传感器监听器
当你完成使用传感器的事情或者当传感器activity pause的时候,确保注销传感器监听器。如果一个传感器的监听器被注册并且它的activity被pause,这个传感器将继续获取数据并且使用电池资源,除非你注销这个传感器。下面的代码展示了如何使用onPause()方法来注销和注册一个监听器:
- private SensorManager mSensorManager;
- ...
- @Override
- protected void onPause() {
- super.onPause();
- mSensorManager.unregisterListener(this);
- }
不要在模拟器上测试你的代码
你目前不能再模拟器上测试你的传感器代码,因为模拟器不能模拟传感器。你必须在一个物理设备上测试你的传感器代码。然后,有,你能使用传感器模拟器来模拟传感器输出。
不要阻塞onSensorChanged()方法
传感器数据可以高速的变化,这意味着系统可能经常调用onSensorChanged(SensorEvent)方法。作为一项最佳的实践,你应该竟可能少的在onSensorChanged(SensorEvent)方法中做事情,所以你没有阻塞它。如果你的应用程序要求你做任何数据过滤或者减少传感器数据,你应该在onSensorChanged(SensorEvent)方法外执行这个工作。
避免使用过时的方法或者传感器类型
几个方法和常量已经被弃用。尤其,TYPE_ORIENTATION传感器类型已经被弃用。为了获取方向数据你应该使用getOrientation()方法替代。同样,TYPE_TEMPERATURE传感器类型已经被弃用。你应该在运行Andorid4.0的设备上使用TYPE_AMBIENT_TEMPERATURE传感器类型替代。
在你使用它们之前验证传感器
在你尝试从它获取数据之前,总是验证在一个传感器在设备上是否存在。不要因为它是一个常用的传感器而简单假设传感器存在。设备厂商没有被要求在它们的设备上提供任何指定的传感器。
仔细选择传感器延迟
当你使用registerListener()方法中注册一个传感器的时候,确保你选择一个适合你的应用程序或者用例的分发率。传感器能非常高速提供数据。允许系统发送额外的你不需要浪费系统资源并使用电池的数据。
---------------------------------------------------------
专注移动开发
Android, Windows Mobile, iPhone, J2ME, BlackBerry, Symbian