Android 傳感器學習(一)——指南針的實現

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_HIGHSENSOR_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的生命周期控制,并且系統并不會在關閉屏幕時關閉傳感器,所以,請注意手動控制好傳感器的開閉,以節省電量。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 我是不是瘋了 瘋。詞典上的釋義為:1、作動詞-神經錯亂、精神失常,如發瘋;2、做形容詞-輕狂、不穩重,那丫頭可瘋了...
    付森堂閱讀 481評論 0 0
  • 總結寫過了,寫計劃。 2015首要任務是根治拖延癥,這大概也是個長期任務吧??戳?片計劃撰寫原則和1篇計劃實施記錄...
    知道茶閱讀 579評論 0 1
  • 同讀一本書《次第花開》2017-3-1-145 正文: 對世上的種種人物、現象,我們以清凈心對待就好了,千萬不要急...
    臺東萬達DDM一店張春燕閱讀 249評論 0 0
  • 下午老姐向我推薦了簡書上的一篇文章:《如何通過自己的愛好賺錢》,后來還專門寫了一篇簡書:《建議三弟寫作的一些想法》...
    macrob閱讀 124評論 1 0