Android平台支持三大類傳感器:
- 運動傳感器:沿三個軸測量加速力和旋轉力。包括:加速度傳感器, 重力傳感器, 陀螺儀, 旋轉矢量傳感器。
- 環境傳感器:測量各種環境參數,例如:溫度和壓力,照明和濕度。包括:氣壓計, 光度計, 溫度計。
- 位置傳感器:測量設備的物理位置。包括:方向傳感器, 地磁傳感器。
其中一些傳感器基於硬件,另一些基於軟件實現。
| Sensor Type | Description | Common Uses |
|---|---|---|
| TYPE_ACCELEROMETER | 硬件 | 三個軸向上設備的加速度m / s2,包括重力。用於動作檢測(抖動,傾斜等)。 |
| TYPE_ACCELEROMETER_UNCALIBRATED | 硬件 | 三個軸向上設備的加速度m / s2,不包括重力。 |
| TYPE_AMBIENT_TEMPERATURE | 硬件 | 以攝氏度(°C)為單位測量環境室溫。 |
| TYPE_GRAVITY | 軟件、硬件 | 三個軸向上設備的加速度m / s2,包括重力。用於動作檢測(抖動,傾斜等)。 |
| TYPE_GYROSCOPE | 硬件、軟件 | 三個軸向中的每一個以rad / s為單位的旋轉速率。用於旋轉檢測(旋轉,轉動等)。 |
| TYPE_LIGHT | 硬件 | 以lux為單位測量環境光水平(照度)。 |
| TYPE_LINEAR_ACCELERATION | 軟件、硬件 | 三個軸向上設備的加速度m / s2,不包括重力。監控單軸加速度。 |
| TYPE_MAGNETIC_FIELD | 硬件 | 以μT為單位測量所有三個物理軸(x,y,z)的環境地磁場,創建指南針。 |
| TYPE_ORIENTATION | 軟件 | 測量設備圍繞所有三個物理軸(x,y,z)旋轉的度數。從API級別3開始,可以通過使用重力傳感器和地磁場傳感器以及getRotationMatrix()方法獲得設備的傾斜矩陣和旋轉矩陣。確定設備位置。 |
| TYPE_PRESSURE | 硬件 | 以hPa或mbar測量環境空氣壓力。監測氣壓變化。 |
| TYPE_PROXIMITY | 硬件 | 測量相對於設備視圖屏幕的對象的接近度(cm)。該傳感器通常用於確定手機是否被握在人的耳朵上。通話期間的電話位置。 |
| TYPE_RELATIVE_HUMIDITY | 硬件 | 以百分比(%)測量相對環境濕度。監測露點,絕對和相對濕度。 |
| TYPE_ROTATION_VECTOR | 軟件、硬件 | 通過提供設備旋轉矢量的三個元素來測量設備的方向。運動檢測和旋轉檢測。 |
| TYPE_TEMPERATURE | 硬件 | 以攝氏度(°C)為單位測量設備的溫度。此傳感器實現因設備而異,並且此傳感器已替換為API級別14中的TYPE_AMBIENT_TEMPERATURE傳感器。監測溫度。 |
| TYPE_STEP_COUNTER | 軟件 | 已激活傳感器最后一次重新啟動以來用戶邁出的步數。 |
4.1、運動傳感器
Android平台提供了幾個傳感器監視設備的運動。加速度傳感器和陀螺儀傳感器始終基於硬件,重力、線性加速度、旋轉矢量、有效運動、計步器和步測器傳感器可能基於硬件,也可能基於軟件。
所有運動傳感器都為每個Sensorevent返回傳感器值的多維數組。例如,在單個傳感器事件期間,加速計返回三個坐標軸的加速度數據,而陀螺儀返回三個坐標軸的旋轉速率數據。這些數據值與其他傳感器事件參數一起以浮點數組(值)形式返回。
4.1.1、重力加速度傳感器
重力傳感器提供一個三維矢量,指示重力的方向和大小。通常,該傳感器用於確定設備在空間中的相對方向。以下代碼說明如何獲取默認重力傳感器的實例:
private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
- 當設備靜止時,重力傳感器的輸出應與加速計的輸出相同。
4.1.2、線性加速度計
線性加速度傳感器提供一個三維矢量,表示沿每個設備軸的加速度,不包括重力。可以使用此值執行手勢檢測。當你想在不受重力影響的情況下獲得加速度數據時,通常使用這個傳感器。該值也可以作為使用航位推算的慣性導航系統的輸入。下面的代碼演示如何獲取默認線性加速度傳感器的實例:
private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
從概念上講,此傳感器根據以下關系提供加速度數據:linear acceleration = acceleration - acceleration due to gravity
4.1.3、旋轉矢量傳感器
旋轉矢量表示設備的角度,該組合為角度和軸線的組合,其中該設備通過圍繞軸(x,y,z)的角度θ旋轉。旋轉矢量的三個元素表示如下:
- x*sin(θ/2)
- y*sin(θ/2)
- z*sin(θ/2)
其中旋轉矢量的大小等sin(θ/2),並且旋轉矢量的方向等於旋轉軸的方向。
旋轉矢量的三個元素等於一個旋轉單位的最后三個分量(cos(θ/2)、xsin(θ/2)、ysin(θ/2)、z*sin(θ/2))。旋轉向量的元素是無單位的。x、y和z軸的定義方式與加速度傳感器相同。參考坐標系具有以下特點:
X被定義為矢量乘積y*z,它在設備的當前位置與地面相切,正方向是右手握手機向身體傾斜。Y與設備當前位置的地面相切,Y正即手機正常使用方向。Z指向天空並垂直於地平面。
4.1.4、有效運動傳感器(significant motion sensor)
每次檢測到有效運動時,有效運動傳感器都會觸發一個事件,然后它會禁用自身。重要運動是可能導致用戶位置改變的運動,例如步行、騎自行車或坐在移動的汽車中。下面的代碼演示如何獲取默認有效運動傳感器的實例以及如何注冊事件偵聽器:
private SensorManager sensorManager;
private Sensor sensor;
private TriggerEventListener triggerEventListener;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
triggerEventListener = new TriggerEventListener() {
@Override
public void onTrigger(TriggerEvent event) {
// Do work
}
};
sensorManager.requestTriggerSensor(triggerEventListener, mSensor);
4.1.5、計步器
計步器提供自上次啟動傳感器以來用戶所采取的步驟數。步進計數器比步進檢測器傳感器有更多的延遲(最多10秒)但更精確。必須聲明 ACTIVITY_RECOGNITION,以便應用程序在運行Android 10(API級別29)或更高版本的設備上使用此傳感器。
4.1.6、使用原始數據
以下傳感器為應用程序提供有關應用於設備的線性和旋轉力的原始數據。為了有效地使用這些傳感器的值,需要過濾掉環境中的因素,例如重力。可能還需要對值的趨勢應用平滑算法以減少噪聲。
概念上,加速度傳感器通過使用以下關系測量施加到傳感器本身(Fs)的力來確定施加到設備(Ad)上的加速度:
然而,重力總是根據以下關系影響測量加速度:
因此,當設備在桌子上時,加速計讀數為g=9.81m/s2。類似地,當設備處於自由落體狀態,其加速度計的讀數為g=0m/s2。因此,要測量裝置的實際加速度,必須從加速度計數據中去除重力的貢獻。這可以通過應用高通濾波器來實現。相反,可以使用低通濾波器來隔離重力。下面的示例演示如何執行此操作:
public void onSensorChanged(SensorEvent event){
// In this example, alpha is calculated as t / (t + dT),
// where t is the low-pass filter's time-constant and
// dT is the event delivery rate.
final float alpha = 0.8;
// Isolate the force of gravity with the low-pass filter.
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
// Remove the gravity contribution with the high-pass filter.
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
}
可以使用許多不同的技術來過濾傳感器數據。上面的代碼示例使用一個簡單的過濾器常數(alpha)創建一個低通過濾器。該濾波器常數來自時間常數(t),它是濾波器添加到傳感器事件的延遲和傳感器事件傳遞率(dt)的粗略表示。代碼示例使用0.8的alpha值進行演示。如果使用此篩選方法,則可能需要選擇其他alpha值。
陀螺儀測量在設備X、Y和Z軸周圍的RAD/S中的旋轉速率。傳感器的坐標系與加速度傳感器的坐標系相同。通常,陀螺儀的輸出隨時間積分,以計算描述角度隨時間變化的旋轉。
// Create a constant to convert nanoseconds to seconds.
private static final float NS2S = 1.0f / 1000000000.0f;
private final float[] deltaRotationVector = new float[4]();
private float timestamp;
public void onSensorChanged(SensorEvent event) {
// This timestep's delta rotation to be multiplied by the current rotation
// after computing it from the gyro sample data.
if (timestamp != 0) {
final float dT = (event.timestamp - timestamp) * NS2S;
// Axis of the rotation sample, not normalized yet.
float axisX = event.values[0];
float axisY = event.values[1];
float axisZ = event.values[2];
// Calculate the angular speed of the sample
float omegaMagnitude = sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
// Normalize the rotation vector if it's big enough to get the axis
// (that is, EPSILON should represent your maximum allowable margin of error)
if (omegaMagnitude > EPSILON) {
axisX /= omegaMagnitude;
axisY /= omegaMagnitude;
axisZ /= omegaMagnitude;
}
// Integrate around this axis with the angular speed by the timestep
// in order to get a delta rotation from this sample over the timestep
// We will convert this axis-angle representation of the delta rotation
// into a quaternion before turning it into the rotation matrix.
float thetaOverTwo = omegaMagnitude * dT / 2.0f;
float sinThetaOverTwo = sin(thetaOverTwo);
float cosThetaOverTwo = cos(thetaOverTwo);
deltaRotationVector[0] = sinThetaOverTwo * axisX;
deltaRotationVector[1] = sinThetaOverTwo * axisY;
deltaRotationVector[2] = sinThetaOverTwo * axisZ;
deltaRotationVector[3] = cosThetaOverTwo;
}
timestamp = event.timestamp;
float[] deltaRotationMatrix = new float[9];
SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
// User code should concatenate the delta rotation we computed with the current rotation
// in order to get the updated rotation.
// rotationCurrent = rotationCurrent * deltaRotationMatrix;
}
標准陀螺儀提供原始旋轉數據,無需對噪聲和漂移(偏差)進行任何濾波或校正。實際上,陀螺儀的噪聲和漂移會引入需要補償的誤差。通常通過監視其他傳感器(如重力傳感器或加速計)來確定漂移(偏差)和噪聲。未標定陀螺儀與陀螺儀相似,只是沒有對旋轉速率應用陀螺儀漂移補償。旋轉速率仍采用工廠校准和溫度補償。未標定陀螺儀可用於方位數據的后處理和融合。
4.2、位置傳感器
4.2.1、游戲旋轉矢量傳感器
除了不使用地磁場,游戲旋轉矢量傳感器與旋轉矢量傳感器完全相同。只是Y 軸不會指向北方,而會指向其他參照。當陀螺儀繞 Z 軸漂移時,該參照可以漂移相同的數量級。游戲旋轉矢量傳感器不使用磁場,因此相對旋轉更准確,且不受磁場變化的影響。以下代碼展示如何獲取默認游戲旋轉矢量傳感器的實例:
private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);
4.2.2、計算設備的屏幕方向
通過計算設備的屏幕方向,可以監測設備相對於地球參照系(具體為磁北極)的位置。以下代碼展示如何計算設備的屏幕方向:
private SensorManager sensorManager;
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
final float[] rotationMatrix = new float[9];
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading);
// Express the updated rotation matrix as three orientation angles.
final float[] orientationAngles = new float[3];
SensorManager.getOrientation(rotationMatrix, orientationAngles);
系統使用設備的地磁場傳感器和加速度計來計算屏幕方向的角度。通過使用這兩個硬件傳感器,系統可提供以下三個屏幕方向角度的數據:
- 方位角(繞 z 軸旋轉的角度)。此為設備當前指南針方向與磁北向之間的角度。如果設備的上邊緣面朝磁北向,則方位角為 0 度;如果上邊緣朝南,則方位角為 180 度。與之類似,如果上邊緣朝東,則方位角為 90 度;如果上邊緣朝西,則方位角為 270 度。
- 俯仰角(繞 x 軸旋轉的角度)。此為平行於設備屏幕的平面與平行於地面的平面之間的角度。如果將設備與地面平行放置,且其下邊緣最靠近您,同時將設備上邊緣向地面傾斜,則俯仰角將變為正值。沿相反方向傾斜(將設備上邊緣向遠離地面的方向移動)將使俯仰角變為負值。值的范圍為 -180 度到 180 度。
- 傾側角(繞 y 軸旋轉的角度)。此為垂直於設備屏幕的平面與垂直於地面的平面之間的角度。如果將設備與地面平行放置,且其下邊緣最靠近您,同時將設備左邊緣向地面傾斜,則側傾角將變為正值。沿相反方向傾斜(將設備右邊緣移向地面)將使側傾角變為負值。值的范圍為 -90 度到 90 度。
建議不要使用來自屏幕方向傳感器的原始數據,而是結合使用 getRotationMatrix() 方法和 getOrientation() 方法來計算屏幕方向值,如以下代碼示例中所示。在這一過程中,您可以使用 remapCoordinateSystem() 方法,將屏幕方向值轉換為應用的參照系。
public class SensorActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private final float[] accelerometerReading = new float[3];
private final float[] magnetometerReading = new float[3];
private final float[] rotationMatrix = new float[9];
private final float[] orientationAngles = new float[3];
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
// You must implement this callback in your code.
}
@Override
protected void onResume() {
super.onResume();
// Get updates from the accelerometer and magnetometer at a constant rate.
// To make batch operations more efficient and reduce power consumption,
// provide support for delaying updates to the application.
// In this example, the sensor reporting delay is small enough such that
// the application receives an update before the system checks the sensor
// readings again.
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer != null) {
sensorManager.registerListener(this, accelerometer,
SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
}
Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
if (magneticField != null) {
sensorManager.registerListener(this, magneticField,
SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
}
}
@Override
protected void onPause() {
super.onPause();
// Don't receive any more updates from either sensor.
sensorManager.unregisterListener(this);
}
// Get readings from accelerometer and magnetometer. To simplify calculations,
// consider storing these readings as unit vectors.
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
System.arraycopy(event.values, 0, accelerometerReading,
0, accelerometerReading.length);
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
System.arraycopy(event.values, 0, magnetometerReading,
0, magnetometerReading.length);
}
}
// Compute the three orientation angles based on the most recent readings from
// the device's accelerometer and magnetometer.
public void updateOrientationAngles() {
// Update rotation matrix, which is needed to update orientation angles.
SensorManager.getRotationMatrix(rotationMatrix, null,
accelerometerReading, mMagnetometerReading);
// "mRotationMatrix" now has up-to-date information.
SensorManager.getOrientation(rotationMatrix, mOrientationAngles);
// "mOrientationAngles" now has up-to-date information.
}
}
4.2.3、使用距離傳感器
private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
通常用於確定人的頭部距手持設備表面的距離(例如,當用戶撥打或接聽電話時)。大多數距離傳感器返回以厘米為單位的絕對距離,但有些僅返回近距離和遠距離值。以下代碼展示如何使用近程傳感器:
public class SensorActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor proximity;
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get an instance of the sensor service, and use that to get an instance of
// a particular sensor.
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
}
@Override
public final void onSensorChanged(SensorEvent event) {
float distance = event.values[0];
// Do something with this sensor data.
}
@Override
protected void onResume() {
// Register a listener for the sensor.
super.onResume();
sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
// Be sure to unregister the sensor when the activity pauses.
super.onPause();
sensorManager.unregisterListener(this);
}
}
4.3、環境傳感器
環境傳感器主要就是光線傳感器,其他在設備上並不常見。
4.3.1、光線傳感器
從光、壓力和溫度傳感器獲取的原始數據通常不需要校准、濾波或修改。以下代碼說明了如何操作:
public class SensorActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor pressure;
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get an instance of the sensor service, and use that to get an instance of
// a particular sensor.
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
pressure = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
}
@Override
public final void onSensorChanged(SensorEvent event) {
float millibarsOfPressure = event.values[0];
// Do something with this sensor data.
}
@Override
protected void onResume() {
// Register a listener for the sensor.
super.onResume();
sensorManager.registerListener(this, pressure, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
// Be sure to unregister the sensor when the activity pauses.
super.onPause();
sensorManager.unregisterListener(this);
}
}
4.4、傳感器坐標系
通常,傳感器框架使用標准的3軸坐標系統來表示數據值。對於大多數傳感器,當設備保持在默認方向時,坐標系是相對於設備屏幕定義的。當設備保持在其默認方向上時,X軸是水平的並且指向右邊,Y軸是垂直的並且指向上,Z軸指向屏幕表面的外側。在這個系統中,屏幕后面的坐標有負的Z值。
了解這個坐標系最重要的一點是,當設備的屏幕方向改變時,軸不會交換,也就是說,傳感器的坐標系永遠不會隨着設備的移動而改變。此行為與OpenGL坐標系的行為相同。另一點需要理解的是,應用程序不能假定設備的自然(默認)方向是縱向。許多平板電腦設備的自然定位是橫向的。傳感器的坐標系總是基於設備的自然方位。
最后,如果應用程序將傳感器數據與屏幕顯示匹配,則需要使用getRotation()方法確定屏幕旋轉,然后使用remapCoordinateSystem()方法將傳感器坐標映射到屏幕坐標。即使清單指定僅顯示縱向,也需要執行此操作。
