大多数Android设备都有内置的传感器用来测量运动, 方向和多种环境状况. 这些传感器可以提供高精度的原始数据, 如果我们想要监测三维设备移动或者位置或者想要监测环境改变的话, 它们都很有用. 栗如, 有些游戏可以通过重力传感器实现用户复杂的手势和运动, 摇晃旋转摆动等. 还有比如一个天气应用可能使用设备的温度传感器和湿度传感器来计算和报告环境参数, 或者旅游APP可能使用地磁传感器和加速计来报告指南针方位.
Android平台支持三类传感器:
l 运动传感器: 这类传感器用三个独立的轴测量加速度和旋转力. 这类包含加速度计, 重力传感器, 陀螺仪和旋转矢量传感器.
l 环境传感器: 这类传感器测量多种环境参数比如环境空气温度和压力, 照明和湿度. 包括气压计, 光度计和温度计.
l 位置传感器: 这类传感器测量设备的物理位置. 包括方向传感器和磁力计.
我们可以访问设备上可用的传感器并通过Android sensor framework来获取原始的传感器数据. Sensorframework提供了很多类和接口让我们可以实现大量的传感器相关的任务. 比如我们可以使用sensor framework做这些事:
l 确定设备上哪些传感器可用.
l 确定一个单独的传感器的能力,比如它的最大范围, 供应商, 电源要求和分辨率.
l 获取传感器原始数据并定义获取原始数据的频率.
l 注册和注销传感器时间监听器.
本文介绍了Android平台上基本传感器的介绍, 也介绍了sensor framework.
Android sensor framework让我们可以访问多种传感器.有些是基于硬件的有些是基于软件的. 硬件实现的传感器是设备内部的物理组件. 它们直接测量指定的环境属性来获取数据, 比如加速度, 磁场强度或者角度变化. 软件实现的传感器并不是物理设备, 虽然它们看起来在模拟物理传感器. 软件传感器从一个或者多个硬件传感器中获得数据, 有时候会调用虚拟传感器和合成传感器. 线性加速传感器和重力传感器就是软件传感器的栗子. 下表总结了Android平台可以支持的传感器.
只有少部分Android设备拥有全部类型的传感器. 比如大多数手持设备和平板拥有加速计和磁力计, 但是很少但设备拥有气压计和温度计. 同样的一个设备可以拥有多于一种传感器, 比如一个设备可以拥有两个重力传感器, 每个有一个不同的范围.
传感器 |
类型 |
描述 |
常用 |
TYPE_ACCELEROMETER |
硬件 |
测量设备三个方向(x,y和z)的加速度, 单位是m/s2(米每秒平方). 包括重力. |
运动检测(晃动, 倾斜等) |
TYPE_AMBIENT_TEMPERATURE |
硬件 |
测量室内环境温度. 单位是摄氏度. |
监测空气温度. |
TYPE_GRAVITY |
软件或者硬件 |
测量重力加速度, 单位m/s2. |
运动检测(晃动, 倾斜等) |
TYPE_GYROSCOPE |
硬件 |
测量设备的旋转速度, 单位是rad/s. 测量三个物理轴. |
旋转检测. |
TYPE_LIGHT |
硬件 |
测量环境光照等级, 单位lx. |
控制屏幕亮度. |
TYPE_LINEAR_ACCELERATION |
软件或者硬件 |
测量一个设备上的三个物理轴的加速度, 不包括重力. |
监测单个轴线的加速度. |
TYPE_MAGNETIC_FIELD |
硬件 |
测量三个物理轴的磁场, 单位是μT |
创建一个指南针. |
TYPE_ORIENTATION |
软件 |
测量一个设备所有三个物理轴的旋转度数. |
确定设备位置. |
TYPE_PRESSURE |
硬件 |
测量环境空气压力, 单位是hPa或者mbar. |
检测气压改变. |
TYPE_PROXIMITY |
硬件 |
测量一个物体与设备屏幕的距离, 单位是cm. 通常用来确定电话是否靠近人耳朵. |
打电话的时候电话的位置. |
TYPE_RELATIVE_HUMIDITY |
硬件 |
测量相对湿度, 单位是百分比. |
监测绝对和相对湿度. |
TYPE_ROTATION_VECTOR |
软件或者硬件 |
通过旋转矢量的三个元素测量设备的方向. |
运动检测和旋转检测. |
TYPE_TEMPERATURE |
硬件 |
测量设备温度, 单位是摄氏度. 不同设备间该传感器实现不同. 在API 14中, 该传感器被TYPE_AMBIENT_TEMPERATURE代替. |
监测温度. |
我们可以使用Sensor Framework访问这些传感器并请求原始数据. Sensor Framework是android.hardware包的一部分, 包含这些的类和接口:
l SensorManager: 我们可以使用该类来创建一个传感器服务的实例. 它提供了多个方法来访问和列举传感器, 注册和注销传感器事件监听器, 获取方位信息. 该类还提供了多种传感器常量, 它们用来报告传感器精确度, 设置数据获取频率和校准传感器.
l Sensor: 我们可以使用该类来创建指定传感器的实例. 它提供了多种方法可以让我们确定传感器的能力.
l SensorEvent: 系统使用该类来创建一个传感器事件对象, 它提供了关于传感器事件的信息. 一个传感器事件对象包含这些信息: 传感器原始数据, 传感器产生的事件, 数据的精度还有事件的时间戳.
l SensorEventListener: 可以使用该接口来创建两个回调方法来接收传感器数值改变或传感器精度改变的提醒(传感器事件).
在一个典型的使用这些传感器相关的APP来完成这两件基本任务:
l 识别传感器和传感器能力: 如果APP拥有某些指定的传感器相关的功能的话, 那么在运行时识别传感器和传感器能力是十分有必要的. 栗如, 我们可能想要识别所有设备中展示的传感器, 并禁用不支持的传感器功能. 同样的, 我们可能想要识别所有的传感器类型, 这样可以有选择的实现传感器, 来保证APP的最佳性能.
l 监测传感器事件: 监测传感器事件是获得传感器原始数据的方法. 一个传感器事件会在每次传感器感知到它测量的参数发生变化的时候发生. 一个传感器事件提供给我们四片信息: 引发该事件的传感器的名字, 事件时间戳, 事件的精度, 还有引发时间的原始数据.
传感器可用性会在不同的设备间变化, 也会在不同的Android版本间变化. 这是因为Android的传感器经常夸版本发布. 栗如, 很多传感器在Android1.5中引入, 但是一些在Android2.3之前并没有实现也不可用. 同样的, 一些传感器在Android2.3和Android4.0引入. 两个传感器已经被更好更新的传感器替代.
下表总结了各种传感器在版本更迭中的变化. 这里只列出了4个平台版本, 因为传感器在这些版本中发生了变化. 被列为不推荐使用的传感器仍然可以在随后的平台使用(提供的传感器必须在设备上..).
传感器 |
Android 4.0 |
Android 2.3 |
Android 2.2 |
Android 1.5 |
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中添加, 但是在Android2.3之前不可用.
带2的表示: 该传感器可用, 但是不推荐使用.
Android sensor framework提供了一些方法让我们可以在运行时很容易的确认设备上有哪些传感器. API也提供了让我们可以确认传感器的能力, 比如它的最大范围, 分辨率, 和它的电源需求. 想要识别设备上的传感器, 首先我们需要获取一个sensor service的参考. 要实现这个, 必须先创建一个SensorManager类的实例, 与其它的系统服务一样, 要用getSystemService()方法来获取, 这次传给它的参数是SENSOR_SERVICE. 栗子:
private SensorManager mSensorManager;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
然后我们就可以通过getSensorList()方法获取设备上的传感器列表, 并使用TYPE_ALL变量. 栗子:
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){
// Success! There's a magnetometer.
}
else {
// Failure! No magnetometer.
}
注意: Android不要求设备生产厂家简历任何指定类型的传感器, 所以设备可以拥有广泛的传感器配置. 除了列出的这些设备传感器, 我们还可以使用Sensor类的公共方法来确定一个独立传感器的能力和属性. 这个功能很有用, 比如我们可以使用getResolution()和getMaximumRange()方法来获取传感器的分辨率和最大测量范围. 还可以使用getPower()方法来获取传感器的电量需求.
如果我们想要优化自己的APP支持不同供应商的传感器和不同版本的传感器, 那么有两个公共方法是非常有效的. 比如如果APP需要检测用户手势如倾斜和震动, 那么应该创建一组数据过滤规则, 并为特定供应商的新设备重力传感器提供优化, 和另一组数据过滤规则并为只有加速度计没有重力传感器的设备提供优化. 下面的代码展示了如何使用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)){
// Use the version 3 gravity sensor.
mSensor = gravSensors.get(i);
}
}
}
else{
// Use theaccelerometer.
if (mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null){
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
else{
// Sorry, thereare no accelerometers on your device.
// You can'tplay this game.
}
}
另一个有用发的方法是getMinDelay(), 它返回了一个传感器可以感应数据的最小时间间隔(单位是微秒). 任何getMinDelay()返回非空值的传感器都是一个”流传感器”(streaming sensor). 流传感器通过固定的时间间隔感应数据, 在Android 2.3版本中引入. 如果一个传感器在getMinDelay()中返回了0, 那么意味着传感器不是一个流传感器, 因为它只有在感应到参数变化的时候才会报告数据.
getMinDelay()方法很有用, 因为它让我们确定了传感器可以获取数据的最大频率. 如果APP的某些功能需要高速率的数据采集或者流传感器, 可以使用该方法来确定一个传感器是否满足这些要求, 然后启动或关闭相关功能.
注意: 传感器的最大数据获取速率并不一定就是sensor framework提供给APP的. Sensor framework通过传感器事件来报告数据, 还有几个别的因素也会影响APP接收传感器事件的频率. 下一小节详解.
想要监测原始传感器数据我们需要通过SensorEventListener实现两个回调方法, onAccuracyChanged()和onSensorChanged(). 当下列事件发生的时候, Android会调用这俩方法:
l 传感器精度变化: 这种情况下系统会调用onAccuracyChanged()方法, 提供新的传感器精度和Sensor对象. 精度由四个静态常量表示: SENSOR_STATUS_ACCURACY_LOW,SENSOR_STATUS_ACCURACY_MEDIUM, SENSOR_STATUS_ACCURACY_HIGH,SENSOR_STATUE_UNRELIABLE.
l 传感器上报了新的数据:这种情况下, 系统会调用onSensorChanged()方法, 它提供了一个SensorEvent对象. SensorEvent对象包含关于新传感器数据的信息, 这其中包括: 传感器精度,生成数据的传感器, 生成数据的时间戳, 还有传感器记录的新数据.
下面这段代码展示了如何使用onSensorChanged()方法来监测亮度传感器的数据变化. 该栗子在main.xml文件中的TextView(名为sensor_data)中显示传感器的原始数据.
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 somethinghere if sensor accuracy changes.
}
@Override
public final void onSensorChanged(SensorEvent event) {
// The lightsensor returns a single value.
// Many sensorsreturn 3 values, one for each axis.
float lux = event.values[0];
// Do somethingwith 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);
}
}
上栗中, 默认数据延迟(SENSOR_DELAY_NORMAL)在注册的时候传给registerListener()作为参数. 数据延迟(或者采样率)控制onSensorChanged()方法通知APP传感器事件的频率. 默认数据延迟适用于监测通常的屏幕方向改变, 使用200000微妙的延迟. 我们可以指定其它数据延迟, 比如SENSOR_DELAY_GAME(2000微秒延迟), SENSOR_DELAY_UI(60000微秒), 或者SENSOR_DELAY_FASTEST(0微秒延迟). 在Android3.0中可以用一个绝对的值指定延迟 (微秒).
我们指定的延迟只是一个建议的延迟. Android系统和其他的APP可以更改这个延迟. 最好的证明方法是指定一个可以指定的最大的延迟, 因为系统通常会使用一个比我们指定的更小的延迟(就是说我们应该使用APP逻辑中可以接受的最慢的采样率). 使用更大的延迟可以降低处理器的消耗, 这样可以节省电量.
没有公共方法可以确定sensor framework发送传感器事件的频率. 但是我们可以使用传感器事件附带的时间戳来计算事件采样率. 我们不应该在设置了采样率(延迟)之后去修改它. 如果处于某些原因需要修改, 那么必须得注销再注册传感器监听器.
还有一件需要留意的事情, 就是上面的栗子中在onResume()和onPause()回调方法中注册和注销传感器事件监听器. 最佳的做法是应该总是禁用那些不需要用到的传感器, 特别是当activity处于pause的状态的时候. 如果不这样做的话可能在短时间内就会耗尽电池的电量, 因为一些传感器需要很大的功率. 在屏幕关闭后系统并不会自动禁用传感器.
Android不会为设备指定一个标准的传感器配置, 这意味着设备开发商可以用它们喜欢的任意传感器配置, 导致设备的传感器可能包含很多配置. 比如Motorola Xoom有一个压力传感器, 但是Samsung Nexus S则没有. 同样, Xoom和Nexus S有陀螺仪, 但是HTC Nexus One没有. 如果APP依赖于一个指定类型的传感器, 那么我们必须确保传感器在设备上存在, APP才能运行成功. 有两种可选的方案用于确认某传感器是否在设备上:
l 在运行时检查传感器并启动或禁用对应的应用功能.
l 使用Google Play过滤器来过滤目标传感器配置.
现在来讨论下这两种方法:
如果APP使用一个指定类型的传感器, 但是不依赖于它, 那么我们可以使用sensor framework在运行时检查传感器, 然后决定APP是否启用相应的功能. 栗如, 一个导航APP可能使用温度传感器, 压力传感器, GPS传感器, 和磁力传感器来显示温度, 气压, 定位和罗盘方位. 如果一个设备没有压力传感器, 我们可以使用sensor framework来在运行时察觉到压力传感器不在, 然后在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! Nopressure sensor.
}
如果我们要在Google Play上fault自己的APP, 那么我们可以在manifest中使用
android:required="true" />
如果添加了这个标签和描述符到APP中, 用户将会只能在他们的设备上拥有加速计的情况下才能看到该APP. 我们应该只在APP完全依赖于指定传感器的时候才使用android:required=”true”. 如果APP使用传感器用于某些功能而不是全部, 那么应该使用android:required=”false”. 这将使APP可以安装到没有该传感器的设备上. 请记住, 如果APP使用了一个指定的传感器, 但是没有传感器的情况下依然可以使用, 那么应该在运行时检测传感器, 并启用或禁用某些APP功能.
通常, sensor framework使用一个标准的三维坐标系来表达数据值. 对于大多数传感器来说, 当设备保持默认方向的时候坐标系的定义是与设备屏幕相关的(如下图). 当设备保持默认方向的时候, X轴表示横向并指向右边, Y轴表示纵向并指向上面, Z轴指向屏幕的正对着的方向. 在该坐标系中, 屏幕背后的Z值是负数.
该坐标系被这些传感器使用:
l 加速度传感器.
l 重力传感器.
l 陀螺仪.
l 线性加速传感器.
l 地磁传感器.
要理解这个坐标系的最重要的一点是, 当屏幕旋转的时候, 各轴并不会交换位置. 就是说, 传感器的坐标系永远都不会随着设备移动而变化. 这种行为跟OpenGL中的坐标系是一样的.另外一个比较重要的是APP一定不能假设设备的自然(默认)方向就是纵向的. 自然方向对于很多平板设备来说是横向的. 传感器坐标系总是基于设备的自然方向的.
最后, 如果APP的传感器匹配了屏幕显示, 我们需要使用getRotation()方法来确定屏幕方向, 并使用remapCoordinateSystem()方法来映射传感器坐标到屏幕坐标. 就算manifest中指定了只支持纵向(portrait-only).更多关于传感器坐标系统的信息, 包括如何处理屏幕方向, 可以参考OnScreen Turn Deserves Another.
注意: 一些传感器和方法使用全球参考框架相关的坐标系统(相对于设备参考框架). 这些传感器和方法返回的设备运动或者位置的数据是相对于地球的. 更多信息可以参考getOrientation()方法, getRotationMatrix()方法, Orientation Sensor和Rotation Vector Sensor.
如果想要设计自己的传感器实现, 那么请留意这小节中讨论的内容. 这些是通过sensor framework访问传感器和获取传感器数据的最佳方案.
当使用传感器之后或者传感器所在的activity暂停了, 要确保注销传感器监听器. 如果传感器监听器处于注册状态并且它的activity暂停了, 那么传感器将会继续获取数据并消耗电池电量, 除非注销它. 下面的代码演示如何在onPause()中注销一个监听器:
private SensorManager mSensorManager;
...
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
当前不能在模拟器上测试传感器的代码, 因为模拟器不能模拟传感器. 我们必须在真机上测试传感器相关的代码. 但是有一些传感器模拟器可以用来模拟传感器输出.
传感器数据可以被高频率的更新, 这意味着系统可能会很频繁的调用onSensorChanged()方法. 最佳的做法是在该方法中做最少的必要的事情以保证不会阻塞它. 如果APP需要实现任何数据过滤或者减少传感器数据, 我们都应该在onSensorChanged()方法外面实现这些工作.
一些方法和常量已经不推荐使用了. 特别是TYPE_ORIENTATION传感器类型. 想要得到方向数据我们应该使用getOrientation()方法代替. 同样, TYPE_TEMPERATURE也不推荐使用了, 在Android4.0版本以上我们应该用TYPE_AMBIENT_TEMPERATURE来代替它.
在使用传感器之前应该总是要确保它们是否可用. 不要因为常用就简单的假设有个传感器存在. . .设备生产商没有必要一定提供任何一种传感器.
当我们用registerListener()方法注册一个传感器的时候, 确保我们为APP选择了一个合适的频率. 传感器可以以很高的频率提供数据. 让系统发送不必要的数据会消耗很多系统资源和电量.
本文主要介绍了传感器的基础知识, 包括种类, 基本用法以及一些使用原则. 之后会详细介绍每种传感器.
管理传感器使用的是SensorManager, 可以通过它来获取设备支持的传感器列表, 以及某种传感器是否存在于设备上. 可以通过它的getDefaultSensor()方法来获取默认的某种传感器, 也就是一个Sensor对象.
当拥有了一个Sensor对象之后就有了对一个传感器的控制权, 我们可以通过SensorManager.registerListener()方法来监听数据改变的事件了. 这里还必须实现SensorEventListener接口, 该接口有两个我们要用到的方法, 就是onAccuracyChanged()和onSensorChanged()分别代表传感器精度变化和数值变化. 这样我们就可以获取传感器数据了.
关于使用原则, 我们要控制传感器数据的更新频率, 可以通过registerListener方法来指定, 不要做不必要的更新以防浪费系统资源和电量. 在使用之前还要记得对传感器是否存在做判断, That’s all!
参考: https://developer.android.com/guide/topics/sensors/sensors_overview.html