Always make sure to disable sensors you don't need, especially when your activity is paused. Failing to do so can drain the battery in just a few hours. Note that the system will not disable sensors automatically when the screen turns off.
確保在你不需要使用傳感器的時候關掉它,特別是在 Activity 暫停的時候。如若不然,幾個小時就會耗盡電池電量。請注意,系統在關閉屏幕的時候是不會自動停止傳感器的。
Google APIs -- SensorManager
簡介
智能手機經過近十年的發展,機身上集成了多種傳感器(Sensor),一些傳感器是基于硬件直接得出結果,另一些則是通過軟件復合運算得出的。常見的重力計、陀螺儀以及距離感應計都屬于硬件傳感器,而方向計則是通過復合計算得出結果,屬于軟件傳感器。
Sensor | Type | Description | Common Uses |
---|---|---|---|
TYPE_ACCELEROMETER | 硬件 | 測量設備在三個物理軸(x,y,z)上的加速度(m/s2),包括重力 | 運動檢測(搖動,傾斜等) |
TYPE_AMBIENT_TEMPERATURE | 硬件 | 測量設備周圍空間的攝氏溫度(℃) | 監測空氣溫度 |
TYPE_GRAVITY | 硬件或軟件 | 測量施加在設備三個物理軸向(x,y,z)的重力加速度(m/s2) | 運動檢測(搖動,傾斜等) |
TYPE_GYROSCOPE | 硬件 | 測量設備圍繞三個物理軸(x,y,z)的旋轉速率(rad/s) | 旋轉檢測(旋轉,轉動等) |
TYPE_LIGHT | 硬件 | 測量環境光亮度等級流明(lx) | 調節屏幕亮度 |
TYPE_LINEAR_ACCELERATION | 硬件或軟件 | 測量設備在三個物理軸(x,y,z)上的加速度(m/s2),包括重力 | 檢測單個軸的加速度 |
TYPE_MAGNETIC_FIELD | 硬件 | 檢測沿三個物理軸(x,y,z)的磁場強度μT | 創建指南針 |
TYPE_ORIENTATION | 軟件 | 測量設備圍繞三個物理軸(x,y,z)的旋轉度。API 等級大于3的可以通過調用重力計和磁場計獲取傾斜矩陣和旋轉矩陣,再結合 getRotationMatrix() 方法獲取方向矩陣 | 確定設備位置 |
TYPE_PRESSURE | 硬件 | 測量周圍的空氣壓力(hPa or mbar) | 檢測氣壓變化 |
TYPE_PROXIMITY | 硬件 | 測量一個物件到屏幕的距離(cm).該傳感器通常用于確定手機是否靠近人耳 | 通話時的設備位置 |
TYPE_RELATIVE_HUMIDITY | 硬件 | 測量周圍的濕度(%) | 檢測露點,絕對和相對濕度 |
TYPE_ROTATION_VECTOR | 軟件或硬件 | 通過提供設備的三個旋轉矢量元素確定設備的方向 | 運動和旋轉檢測 |
調用
獲取傳感器
要使用傳感器,需要先獲取手機的傳感器服務。通過getSystemService
創建一個SensorManager
實例:
private SensorManager mSensorManager;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
接著,可以傳入TYPE_ALL
參數獲取傳感器列表:
List<Sensor> deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
如果你想獲取指定類型的傳感器列表,可以通過傳入其他的類型常量,如TYPE_GYROSCOPE
或者TYPE_GRAVITY
來取代TYPE_ALL
.
所有支持的傳感器,并不會在所有的手機上都存在,類似于氣壓計這樣的傳感器,目前也只在高端機型上存在。所以在調取傳感器的時候,需要對其做代碼檢查,例如
private SensorManager mSensorManager;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if(mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null){
//Success!存在磁力計
}else{
//Failure!不存在磁力計
}
監控傳感器事件
要監控傳感器事件,必須實現SensorEventListener
接口的兩個回調方法:onAccuracyChanged()
和onSensorChanged()
.
A sensor's accuracy changes.
傳感器的精度變化會調用onAccuracyChanged()
方法。精度有四個常量:SENSOR_STATUS_ACCURACY_LOW
,SENSOR_STATUS_ACCURACY_MEDIUM
,SENSOR_STATUS_ACCURACY_HIGH
和SENSOR_STATUS_UNRELIABLE
。
A sensor reports a new value.
傳感器返回新的數據時候會調用onSensorChanged()
方法,提供SensorEvent
對象,該對象包含傳感器的數據,包含數據精度,傳感器,數據生成時間和傳感器生成的數據。
接下來將通過一個指南針的例子來介紹傳感器事件監聽的運行機制。通過對 API 的查詢了解,SENSOR_ORIENTATION
已經在 API level 8廢棄了,想要通過getDefaultSensor()
方法直接獲取方向參數已經不可取。官方給出的替代方法是一個靜態(static)方法getOrientation(float[] R, float[] values)
.
getOrientation
基于旋轉矩陣獲取設備的方向。
請求參數 | |
---|---|
R | float:旋轉矩陣,通過getRotationMatrix(float[], float[], float[], float[]) 方法獲取 |
values | float:包含三個float的數組 |
返回參數 | |
---|---|
float[] | 返回的數組 |
getRotationMatrix
getRotationMatrix(float[] R, float[] I, float[] gravity, float[] geomagnetic)
計算傾斜矩陣I和旋轉矩陣R并將其通過矢量變換從設備的坐標系轉換成真實世界正交坐標系:
- X 定義為Y·Z的向量積(在設備所在的位置和地面相切,并大致指向東方)。
- Y 在設備所在的位置與地面相切,并指向磁北極。
-
Z 指向天空并垂直于地面。
正交坐標系
其中
請求參數 | |
---|---|
R | float:9個float參數的數組,保存旋轉矩陣R的數據。R可以為空 |
I | float:9個float參數的數組,保存傾斜矩陣I的數據。I可以為空 |
gravity | float:3個float參數的數組,包含基于設備坐標系的重力矢量。使用類型為TYPE_ACCELEROMETER 的傳感器返回的數據 |
geomagnetic | float:3個float參數的數組,包含基于設備坐標系的磁場矢量。使用類型為TYPE_MAGNETIC_FIELD 的傳感器返回的數據 |
返回參數 | |
---|---|
boolean |
true 表示成功,false 表示失敗(當設備處于自由落體狀態)。自由落體的界定,當設備承受的重力小于標稱值的1/10時,視為自由落體。失敗時,輸出的矩陣值不會修改 |
構建一個指南針
在現實世界的正交坐標系中,磁北極是固定方向的。想要構造一個指南針應用,最直觀的思考是獲取到設備朝向和所處位置磁北極方向的夾角。這個時候,直觀的思考就是正確的思考,如何獲取這樣一個夾角呢。通過上面介紹的 API 來看,getRotationMatrix()
方法,能夠獲得設備的旋轉矩陣和傾斜矩陣。旋轉矩陣是一個33或者44的矩陣,通過getOrientation()
方法就能直接獲得設備關于正交系的三軸旋轉數據。設備的指向必然是設備關于z軸的旋轉角度。如何取值可以參考源碼或者文檔:
- values[0]: Azimuth, angle of rotation about the -z axis. This value represents the angle between the device's y axis and the magnetic north pole. When facing north, this angle is 0, when facing south, this angle is π. Likewise, when facing east, this angle is π/2, and when facing west, this angle is -π/2. The range of values is -π to π.
- values[1]: Pitch, angle of rotation about the x axis. This value represents the angle between a plane parallel to the device's screen and a plane parallel to the ground. Assuming that the bottom edge of the device faces the user and that the screen is face-up, tilting the top edge of the device toward the ground creates a positive pitch angle. The range of values is -π to π.
- values[2]: Roll, angle of rotation about the y axis. This value represents the angle between a plane perpendicular to the device's screen and a plane perpendicular to the ground. Assuming that the bottom edge of the device faces the user and that the screen is face-up, tilting the left edge of the device toward the ground creates a positive roll angle. The range of values is -π/2 to π/2.
可知,此時此刻的values[0]
正是我們想要的值?;貧w到代碼的世界,是這樣的:
public class SensorActivity extends Activity implements SensorEventListener{
//sensor object
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Sensor mMagneticField;
//sensor data
private float[] r = new float[9]; //rotation matrix
private float[] values = new float[3] //orientation values
private float[] accelerometerValues = new float[3] //data of acclerometer sensor
private float[] magneticFieldValues = new fooat[3] //data of magnetic field sensor
@Ovrride
protected void onCreate(Bundle savedInstanceState) {
...
//init sensor
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagneticField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
...
}
@Override
protected void onResume() {
super.onResume();
//regist listener
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
mSensorManager.registerListener(this, mMagneticField, SensorManager.SENSOR_DELAY_NORMAL);
...
}
@Override
protected void onPause() {
super.onPause();
//unregist listener
mSensorManager.unregisterListener(this);
}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
accelerometerValues = sensorEvent.values;
}
if (sensorEvent.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magneticFieldValues = sensorEvent.values;
}
SensorManager.getRotationMatrix(r, null, accelerometerValues, magneticFieldValues);
SensorManager.getOrientation(r, values);
values[0] = (float) Math.toDegrees(values[0]);
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
}
整個過程是很流暢的,由于傳感器本身是不受Activity的生命周期控制,并且系統并不會在關閉屏幕時關閉傳感器,所以,請注意手動控制好傳感器的開閉,以節省電量。