浅谈下方向传感器已弃用的Sensor.TYPE_ORIENTATION的代替方法

3,032 阅读6分钟

最近心血来潮,跟着写了个指南针的小项目,途中当向SensorManager注册监听器的时候遇见提示,在Android 4.0.3之后该方法就已被弃用,故今天来谈下它的代替方法。

源码原理

首先我们点进去Sensor.TYPE_ORIENTATION的源码进去看

    /**
     * A constant describing an orientation sensor type.
     * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
     * for more details.
     *
     * @deprecated use {@link android.hardware.SensorManager#getOrientation
     *             SensorManager.getOrientation()} instead.
     */
    @Deprecated
    public static final int TYPE_ORIENTATION = 3;

从注解中可得知,谷歌推荐我们用SensorManager.getOrientation()方法来代替,所以接下来我们在跟进这个方法看看。

    /**
     * Computes the device's orientation based on the rotation matrix.
     * <p>
     * When it returns, the array values are as follows:
     * <ul>
     * <li>values[0]: <i>Azimuth</i>, 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 &pi;.
     *                Likewise, when facing east, this angle is &pi;/2, and
     *                when facing west, this angle is -&pi;/2. The range of
     *                values is -&pi; to &pi;.</li>
     * <li>values[1]: <i>Pitch</i>, 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 -&pi; to &pi;.</li>
     * <li>values[2]: <i>Roll</i>, 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 -&pi;/2 to &pi;/2.</li>
     * </ul>
     * <p>
     * Applying these three rotations in the azimuth, pitch, roll order
     * transforms an identity matrix to the rotation matrix passed into this
     * method. Also, note that all three orientation angles are expressed in
     * <b>radians</b>.
     *
     * @param R
     *        rotation matrix see {@link #getRotationMatrix}.
     *
     * @param values
     *        an array of 3 floats to hold the result.
     *
     * @return The array values passed as argument.
     *
     * @see #getRotationMatrix(float[], float[], float[], float[])
     * @see GeomagneticField
     */
    public static float[] getOrientation(float[] R, float[] values) {
        /*
         * 4x4 (length=16) case:
         *   /  R[ 0]   R[ 1]   R[ 2]   0  \
         *   |  R[ 4]   R[ 5]   R[ 6]   0  |
         *   |  R[ 8]   R[ 9]   R[10]   0  |
         *   \      0       0       0   1  /
         *
         * 3x3 (length=9) case:
         *   /  R[ 0]   R[ 1]   R[ 2]  \
         *   |  R[ 3]   R[ 4]   R[ 5]  |
         *   \  R[ 6]   R[ 7]   R[ 8]  /
         *
         */
        if (R.length == 9) {
            values[0] = (float) Math.atan2(R[1], R[4]);
            values[1] = (float) Math.asin(-R[7]);
            values[2] = (float) Math.atan2(-R[6], R[8]);
        } else {
            values[0] = (float) Math.atan2(R[1], R[5]);
            values[1] = (float) Math.asin(-R[9]);
            values[2] = (float) Math.atan2(-R[8], R[10]);
        }

        return values;
    }

这个方法注解很长,方法体内部的逻辑是矩阵的运算,这个我们不深究,主要来看一下注解中参数的介绍

     * @param R
     *        rotation matrix see {@link #getRotationMatrix}.
     *
     * @param values
     *        an array of 3 floats to hold the result.

可见调用此方法后,values数组就存放了计算结果。而R参数的话再跟进到getRotationMatrix()方法里看

     * @param R
     *        is an array of 9 floats holding the rotation matrix <b>R</b> when
     *        this function returns. R can be null.
     *        <p>
     *
     * @param I
     *        is an array of 9 floats holding the rotation matrix <b>I</b> when
     *        this function returns. I can be null.
     *        <p>
     *
     * @param gravity
     *        is an array of 3 floats containing the gravity vector expressed in
     *        the device's coordinate. You can simply use the
     *        {@link android.hardware.SensorEvent#values values} returned by a
     *        {@link android.hardware.SensorEvent SensorEvent} of a
     *        {@link android.hardware.Sensor Sensor} of type
     *        {@link android.hardware.Sensor#TYPE_ACCELEROMETER
     *        TYPE_ACCELEROMETER}.
     *        <p>
     *
     * @param geomagnetic
     *        is an array of 3 floats containing the geomagnetic vector
     *        expressed in the device's coordinate. You can simply use the
     *        {@link android.hardware.SensorEvent#values values} returned by a
     *        {@link android.hardware.SensorEvent SensorEvent} of a
     *        {@link android.hardware.Sensor Sensor} of type
     *        {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD
     *        TYPE_MAGNETIC_FIELD}.
     public static boolean getRotationMatrix(float[] R, float[] I,
            float[] gravity, float[] geomagnetic) {
            ......
    }

这个方法跟上面的getOrientation()方法一样,我们依然不细究方法内部的实现,而是着重看参数的介绍。可见R参数是存放getRotationMatrix()方法计算出来的矩阵结果的,而I我们getOrientation()方法用不到,所以可以将其置为null。而gravity和geomagnetic两个参数,我们可以通过加速度传感器Sensor#TYPE_ACCELEROMETER和地磁场传感器Sensor#TYPE_MAGNETIC_FIELD来获得值。具体用法如下

    private SensorManager mSensorManager;
    private SensorEventListener mSensorEventListener;
    private Sensor accelerometer;//加速度传感器
    private Sensor magnetic;//地磁场传感器
    
    //getRotationMatrix()方法需要的gravity参数
    private float[] accelerometerValues=new float[3];
    //getRotationMatrix()方法需要的geomagnetic参数
    private float[] magneticValues=new float[3];

    mSensorManager=(SensorManager)getSystemService(SENSOR_SERVICE);
    //初始化加速度传感器
    accelerometer=mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    //初始化地磁场传感器
    magnetic=mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    //初始化监听器
    mSensorEventListener=new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent event) {
            if(event.sensor.getType()==Sensor.TYPE_ACCELEROMETER){
                accelerometerValues=event.values;
            }
            if(event.sensor.getType()==Sensor.TYPE_MAGNETIC_FIELD){
                magneticValues=event.values;
            }
        }
        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {

        }
    };
    
    mSensorManager.registerListener(mSensorEventListener,accelerometer,SensorManager.SENSOR_DELAY_GAME);
    mSensorManager.registerListener(mSensorEventListener,magnetic,SensorManager.SENSOR_DELAY_GAME);

先初始化两个传感器,然后在监听器里判断event时间属于的传感器类型,将值放于相应的数组里。最后记得注册监听者。当我们获取到这两个数组后,我们就可以通过调用之前所讲的getOrientation()方法和getRotationMatrix()方法来得到我们所需的值,用法如下:

    //values用来存储getOrientation方法的返回值,同时R作为getOrientation方法的参数
    float[] values=new float[3];
    //R用来存储getRotationMatrix方法的返回值
    float[] R=new float[9];
    SensorManager.getRotationMatrix(R,null,accelerometerValues,magneticValues);
    SensorManager.getOrientation(R,values);

总结

最后总结一下用法,首先实例化两个传感器,加速度传感accelerometer器和地磁场传感器magnetic,然后在监听器代码里获取相对应的数组值,代入getRotationMatrix()方法得到R数组的值,再将R数组的值传入getOrientation()方法得到我们所需要的values数组。

两种用法的比较

首先是用弃用的Sensor.TYPE_ORIENTATION的代码示例

public class MainActivity extends AppCompatActivity {

    private SensorManager mSensorManager;
    private SensorEventListener mSensorEventListener;
    private compassView mCompassView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mCompassView=findViewById(R.id.compass);

        mSensorManager=(SensorManager)getSystemService(SENSOR_SERVICE);
        mSensorEventListener=new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent event) {
                float val=event.values[0];
                mCompassView.setVal(val);
            }
            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {

            }
        };
        mSensorManager.registerListener(mSensorEventListener,mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),SensorManager.SENSOR_DELAY_GAME);
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        mSensorManager.unregisterListener(mSensorEventListener);
    }

}

其中监听器代码中的mCompassView.setVal(val);这一行是将得到的角度值传入我的指南针View中从而实现View的更新,读者进行忽略即可。接下来是用谷歌推荐的方法的代码示例

public class MainActivity extends AppCompatActivity {


    private SensorManager mSensorManager;
    private SensorEventListener mSensorEventListener;
    private Sensor accelerometer;//加速度传感器
    private Sensor magnetic;//地磁场传感器
    private compassView mCompassView;

    //getRotationMatrix()方法需要的gravity参数
    private float[] accelerometerValues=new float[3];
    //getRotationMatrix()方法需要的geomagnetic参数
    private float[] magneticValues=new float[3];


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mCompassView=findViewById(R.id.compass);

        mSensorManager=(SensorManager)getSystemService(SENSOR_SERVICE);
        //初始化加速度传感器
        accelerometer=mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        //初始化地磁场传感器
        magnetic=mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        //初始化监听器
        mSensorEventListener=new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent event) {
                if(event.sensor.getType()==Sensor.TYPE_ACCELEROMETER){
                    accelerometerValues=event.values;
                }
                if(event.sensor.getType()==Sensor.TYPE_MAGNETIC_FIELD){
                    magneticValues=event.values;
                }

                //values用来存储getOrientation方法的返回值,同时R作为getOrientation方法的参数
                float[] values=new float[3];
                //R用来存储getRotationMatrix方法的返回值
                float[] R=new float[9];
                SensorManager.getRotationMatrix(R,null,accelerometerValues,magneticValues);
                SensorManager.getOrientation(R,values);
                //提取数据
                float val=(float)Math.toDegrees(values[0]);
                //因为用此方法取得的val会有负数,相比于用TYPE_ORIENTATION会有差别,所以进行调整
                //TYPE_ORIENTATION取得的值是0~360,而谷歌推荐的方法是-180~180
                if(val<0)
                    val=val+(float)360.0;
                mCompassView.setVal(val);
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {

            }
        };

        mSensorManager.registerListener(mSensorEventListener,accelerometer,SensorManager.SENSOR_DELAY_GAME);
        mSensorManager.registerListener(mSensorEventListener,magnetic,SensorManager.SENSOR_DELAY_GAME);
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        mSensorManager.unregisterListener(mSensorEventListener);
    }

}

这里说一下有个小坑,就是第二种方法得到的values数组是需要进行处理才能提取的,而且角度的取值范围跟TYPE_ORIENTATION不一样,TYPE_ORIENTATION的是逆时针的0~360°,而新方法是逆时针0~180,-180~0,所以在这我对其进行了处理再传入到CompassView里。

因为我是第一次写博客,目的是记录下自己的学习过程中遇到的值得一写的问题,所以如果有差错敬请各位帮忙指出。