位置感應器

Android 平台提供兩種感應器,可讓您判斷裝置的位置:地磁場感應器和加速計。Android 平台也提供感應器,可讓您判斷裝置臉部與物體之間的距離 (稱為「鄰近感應器」)。地磁場感應器和鄰近感應器採硬體為基礎。多數手機和平板電腦製造商都配備地磁場感應器。同樣地,手機製造商通常會提供鄰近感應器,用於判斷何時將手機靠近使用者 (例如通話期間)。如要判斷裝置的螢幕方向,您可以使用裝置加速計和地理磁場感應器的讀數。

注意:方向感應器已在 Android 2.2 (API 級別 8) 中淘汰,且方向感應器類型在 Android 4.4W (API 級別 20) 中已淘汰。

位置感應器可協助您判斷裝置在世界參考影格中的實際位置。舉例來說,您可以將地磁場感應器與加速計搭配使用,藉此判斷裝置相對於磁性北極的定位。您也可以在應用程式參考影格中,使用這些感應器來判斷裝置的螢幕方向。位置感應器通常無法用於監控裝置移動或動作,例如搖晃、傾斜或推手 (詳情請參閱「動作感應器」)。

地磁場感應器和加速計會針對每個 SensorEvent,傳回感應器值的多維陣列。舉例來說,地理磁場感應器會在單一感應器事件期間,為三個座標軸分別提供地理磁場強度值。同樣地,加速計感應器則會測量裝置在感應器事件期間套用的加速度。如要進一步瞭解感應器使用的座標系統,請參閱「 感應器座標系統」。鄰近感應器為每個感應器事件提供單一值。表 1 摘要列出 Android 平台支援的位置感應器。

表 1. Android 平台支援的位置感應器。

感應器 感應器事件資料 說明 測量單位
TYPE_GAME_ROTATION_VECTOR SensorEvent.values[0] 沿著 X 軸的旋轉向量元件 (x * sin(詳細說明/2))。 無單位
SensorEvent.values[1] 沿著 Y 軸的旋轉向量元件 (y * sin(詳細說明/2))。
SensorEvent.values[2] 沿著 Z 軸的旋轉向量元件 (z * sin(詳細說明/2))。
TYPE_GEOMAGNETIC_ROTATION_VECTOR SensorEvent.values[0] 沿著 X 軸的旋轉向量元件 (x * sin(詳細說明/2))。 無單位
SensorEvent.values[1] 沿著 Y 軸的旋轉向量元件 (y * sin(詳細說明/2))。
SensorEvent.values[2] 沿著 Z 軸的旋轉向量元件 (z * sin(詳細說明/2))。
TYPE_MAGNETIC_FIELD SensorEvent.values[0] 沿著 X 軸的地磁場強度。 微 T
SensorEvent.values[1] Y 軸上的地磁場強度。
SensorEvent.values[2] 沿著 Z 軸的地磁場強度。
TYPE_MAGNETIC_FIELD_UNCALIBRATED SensorEvent.values[0] 沿著 X 軸進行地磁場強度 (不含硬鐵校正)。 微 T
SensorEvent.values[1] 沿著 Y 軸進行地磁場強度 (不含硬鐵校正)。
SensorEvent.values[2] 沿著 Z 軸的地磁場強度 (不含硬鐵校正)。
SensorEvent.values[3] 沿著 X 軸的鐵偏誤估算。
SensorEvent.values[4] 沿著 Y 軸的鐵偏誤估算。
SensorEvent.values[5] 沿著 Z 軸的鐵偏誤估算。
TYPE_ORIENTATION1 SensorEvent.values[0] 方位角 (Z 軸環繞的角度)。
SensorEvent.values[1] 傾斜 (X 軸周圍的角度)。
SensorEvent.values[2] 旋轉 (圍繞 Y 軸的角度)。
TYPE_PROXIMITY SensorEvent.values[0] 與物體之間的距離2 公分

1此感應器已在 Android 2.2 (API 級別 8) 中淘汰,且此感應器類型已在 Android 4.4W (API 級別 20) 中淘汰。感應器架構提供了另一種獲取裝置方向的方法,詳情請參閱「計算裝置螢幕方向」一節。

2 某些鄰近感應器僅提供附近和遠處的二進位值。

使用遊戲旋轉向量感應器

遊戲旋轉向量感應器與旋轉向量感應器相同,但不使用地磁場。因此,Y 軸不是指向北方,而是另一個參照。這個參照可以按照與 Z 軸周圍的陀螺儀相同的規模偏移大小。

由於遊戲旋轉向量感應器不會使用磁場,因此相對旋轉更準確,不受磁場變更的影響。如果不在意北方的位置,請在遊戲中使用這個感應器,因為正規旋轉向量依附於磁場,因此不符合您的需求。

以下程式碼說明如何取得預設遊戲旋轉向量感應器的例項:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);

使用地磁旋轉向量感應器

地磁旋轉向量感應器與旋轉向量感應器類似,但未使用陀螺儀。此感應器的準確度低於一般旋轉向量感應器,但耗電量會降低。只有在你想要在背景收集旋轉資訊,而不使用過多電池時,才使用這個感應器。這個感應器在搭配批次處理時最實用。

以下程式碼說明如何取得預設地磁旋轉向量感應器的例項:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR);

計算裝置螢幕方向

計算裝置的螢幕方向後,您就可以監控裝置相對於地球參考影格 (尤其是磁性北極) 的位置。以下程式碼說明如何計算裝置的螢幕方向:

Kotlin

private lateinit var sensorManager: SensorManager
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading)

// Express the updated rotation matrix as three orientation angles.
val orientationAngles = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientationAngles)

Java

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 軸的旋轉角度)。此為平面與裝置螢幕平行的角度,且平面與地面平行的角度。如果裝置與地面平行,且底部邊緣靠近地面,將裝置上側傾斜朝地面,則會將傾斜角度變為正。反向傾斜 (將裝置的頂端邊緣從地面上移開) 會導致傾斜角度變為負。這個值的範圍介於 -90 度到 90 度之間。
  • 擲骰 (Y 軸旋轉角度)。這是指與裝置螢幕直立的飛機角度和與地面垂直的飛機之間角度。如果您將裝置與地面平行,且底部邊緣靠近地面,將裝置左側邊緣朝向地面傾斜,旋轉角度就會變為正數。往反方向傾斜 (將裝置的右側邊緣朝地面) 時,旋轉角度會變為負。這個值的範圍介於 -180 度到 180 度之間。

注意:感應器的捲軸定義已變更,可反映地質感應器生態系統中絕大部分的實作項目。

請注意,這些角度會從與飛行時使用的不同座標系統 (做偏轉、傾斜和滾動) 不同。在飛行系統中,X 軸是沿著飛機的長邊 (從尾到鼻子)。

方向感應器會處理加速計和地磁場感應器的原始感應器資料,藉此取得資料。由於涉及大量處理作業,方向感應器的準確度和精確度會降低。具體來說,這個感應器只有在旋轉角度為 0 時才能穩定運作。因此,方向感應器已在 Android 2.2 (API 級別 8) 中淘汰,且螢幕方向感應器類型已在 Android 4.4W (API 級別 20) 中淘汰。建議您不要使用螢幕方向感應器的原始資料,而是將 getRotationMatrix() 方法與 getOrientation() 方法搭配使用來計算方向值,如以下程式碼範例所示。在這項程序中,您可以使用 remapCoordinateSystem() 方法,將方向值轉譯為應用程式的參考架構。

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private val accelerometerReading = FloatArray(3)
    private val magnetometerReading = FloatArray(3)

    private val rotationMatrix = FloatArray(9)
    private val orientationAngles = FloatArray(3)

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
        // You must implement this callback in your code.
    }

    override fun 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.
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also { accelerometer ->
            sensorManager.registerListener(
                    this,
                    accelerometer,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
        sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField ->
            sensorManager.registerListener(
                    this,
                    magneticField,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
    }

    override fun 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 fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
            System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)
        } else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
        }
    }

    // Compute the three orientation angles based on the most recent readings from
    // the device's accelerometer and magnetometer.
    fun updateOrientationAngles() {
        // Update rotation matrix, which is needed to update orientation angles.
        SensorManager.getRotationMatrix(
                rotationMatrix,
                null,
                accelerometerReading,
                magnetometerReading
        )

        // "rotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, orientationAngles)

        // "orientationAngles" now has up-to-date information.
    }
}

Java

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, magnetometerReading);

        // "rotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, orientationAngles);

        // "orientationAngles" now has up-to-date information.
    }
}

您通常不需要對裝置的原始方向角度進行資料處理作業或篩選,只須將感應器的座標系統轉譯成應用程式參考影格。

使用地磁場感應器

地磁場感應器可讓你監測地球磁場的變化。以下程式碼說明如何取得預設地磁場感應器的例項:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

注意: 如果應用程式指定的是 Android 12 (API 級別 31) 以上版本,則這個感應器具有頻率限制

這個感應器會為三個座標軸提供原始磁場強度資料 (以 μT 為單位)。通常你不需要直接使用這個感應器,相對地,您可以使用旋轉向量感應器來判斷原始旋轉動作,也可以使用加速計和地磁場感應器,搭配 getRotationMatrix() 方法取得旋轉矩陣和推論矩陣。接著,您可以將這些矩陣與 getOrientation()getInclination() 方法搭配使用,取得方位角和地磁的納入資料。

注意: 測試應用程式時,請以 8 字形揮動裝置,藉此提高感應器的準確度。

使用未校正的磁力儀

未校正的磁力儀與地磁場感應器類似,差別在於磁場上不會套用硬鐵校正。工廠校正和溫度補償仍適用於磁場。未經校正的磁力儀有助於處理不佳的鐵質估計。一般來說,geomagneticsensor_event.values[0] 會靠近 uncalibrated_magnetometer_event.values[0] - uncalibrated_magnetometer_event.values[3]。也就是

calibrated_x ~= uncalibrated_x - bias_estimate_x

注意:未經校正的感應器可提供更多原始結果,且可能包含部分偏誤,但透過校正功能套用的校正後,其測量結果所包含的跳轉次數較少。有些應用程式可能偏好這些未校正的結果,因為這樣較順暢且更可靠。例如,如果應用程式嘗試自行進行感應器融合,引入校正實際上可能會扭曲結果。

除了磁場外,未校正的磁力儀也會提供每個軸的預估硬鐵偏誤。以下程式碼顯示如何取得預設未校正的磁力計的執行個體:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);

使用鄰近感應器

鄰近感應器可讓你判斷物體與裝置之間的距離。以下程式碼說明如何取得預設鄰近感應器的例項:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

鄰近感應器通常用於判斷某人頭部與手機錶面之間的距離 (例如使用者正在撥打或接聽電話時)。大部分的鄰近感應器會傳回絕對距離 (以公分為單位),但有些只會傳回近似值。

注意:在某些裝置型號上,鄰近感應器位於螢幕下方。如果在螢幕開啟的情況下啟用,可能會導致畫面上出現閃爍的圓點。

以下程式碼說明如何使用鄰近感應器:

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private var proximity: Sensor? = null

    public override fun onCreate(savedInstanceState: Bundle?) {
        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 = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
    }

    override fun onSensorChanged(event: SensorEvent) {
        val distance = event.values[0]
        // Do something with this sensor data.
    }

    override fun onResume() {
        // Register a listener for the sensor.
        super.onResume()

        proximity?.also { proximity ->
            sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL)
        }
    }

    override fun onPause() {
        // Be sure to unregister the sensor when the activity pauses.
        super.onPause()
        sensorManager.unregisterListener(this)
    }
}

Java

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);
    }
}

注意:部分鄰近感應器會傳回代表「附近」或「附近」的二進位值。在此情況下,感應器通常會回報最遠狀態的最大範圍值,鄰近狀態則較小。一般值通常是大於 5 公分的值,但這個值會因感應器而不同。您可以使用 getMaximumRange() 方法判斷感應器的最大範圍。

另請參閱