最近心血来潮,跟着写了个指南针的小项目,途中当向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 π.
* Likewise, when facing east, this angle is π/2, and
* when facing west, this angle is -π/2. The range of
* values is -π to π.</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 -π to π.</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 -π/2 to π/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里。
因为我是第一次写博客,目的是记录下自己的学习过程中遇到的值得一写的问题,所以如果有差错敬请各位帮忙指出。