android 指南针 - UserWang/Android-Notes GitHub Wiki

目的:通过指南针应用来学习SensorManager、LocationManger的使用以及对android 6.0动态权限的适配

###一、通过android的方向传感器获取手机方位 通过对比前一刻方位和现在手机方位算出手机旋转的角度,然后根据手机实际旋转的角度去旋转指南针的图片。

一般情况下,在android系统中获取手机的方位信息是很简单的事情,在api中有TYPE_ORIENTATION常量,可以像得到加速度传感器那样                            
得到方向传感器mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
然而从官网的描述中我们可以看到:“TYPE_ORIENTATION  This constant was deprecated in API level 8. use SensorManager.getOrientation() instead. ”
即这种方式已经过期啦,不建议使用!Google建议我们在应用程序中使用SensorManager.getOrientation()来获得原始数据。

看一下官网中对于getOrientation()的定义:

###public static float[] getOrientation (float[] R, float[] values) Computes the device's orientation based on the rotation matrix. When it returns, the array values is filled with the result:

  • values[0]: azimuth, rotation around the -Z axis, i.e. the opposite direction of Z axis.(azimuth 方向角,但用(磁场+加速度)得到的数据范围是(-180~180),也就是说,0表示正北,90表示正东,180/-180表示正南,-90表示正西。而直接通过方向感应器数据范围是(0~359)360/0表示正北,90表示正东,180表示正南,270表示正西。)
  • values[1]: pitch, rotation around the -X axis, i.e the opposite direction of X axis.( pitch 倾斜角 即由静止状态开始,前后翻转)
  • values[2]: roll, rotation around the Y axis. (roll 旋转角 即由静止状态开始,左右翻转)

Applying these three intrinsic rotations in azimuth, pitch and roll order transforms identity matrix to the rotation
matrix given in input R. All three angles above are in radians and positive in the counter-clockwise direction. Range of output is: azimuth from -π to π, pitch from -π/2 to π/2 and roll from -π to π.

第一个参数是R[] 是一个旋转矩阵,用来保存磁场和加速度的数据,可以理解为这个函数的传入值,通过它这个函数给你求出方位角。 第二个参数就是这个函数的输出了,他有函数自动为我们填充,这就是我们想要的。 这个R[]呢,是通过SensorManager的另一个函数getRotationMatrix 得到的,

public static boolean getRotationMatrix (float[] R, float[] I, float[] gravity, float[] geomagnetic)

解释一下这四个参数,第一个就是我们需要填充的R数组,大小是9 第二个是是一个转换矩阵,将磁场数据转换进实际的重力坐标中 一般默认情况下可以设置为null 第三个是一个大小为3的数组,表示从加速度感应器获取来的数据 在onSensorChanged中 第四个是一个大小为3的数组,表示从磁场感应器获取来的数据 在onSensorChanged中

使用SensorManger还要注意一点,当不需要方向传感器的时候,要其实关闭,尤其是Activity 调用了onPause()生命周期之后,否则会非常耗电!

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.

###二、获取海拔高度

获取海拔高度用到的是locationManager,代码写的很清楚,但是这里要注意的是,如果你的API版本大于23,在使用locationManger的时候,可能会看到以下的报错信息:

Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException

我们都知道,这是android 6.0 的新特性,当APP需要使用一些敏感权限时,会对用户进行提示,同时代码中也要做相应处理

 /**
 * 适配android 6.0 检查权限
 */
private boolean checkLocationPermission() {
    if (Build.VERSION.SDK_INT >= 23) {
        return (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
                PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) ==
                        PackageManager.PERMISSION_GRANTED);
    }

    return true;

}

主要代码:

/**
 * 指南针Activity
 * Created by Jundooong on 2016/05/10.
 */
public class MainActivity extends AppCompatActivity implements SensorEventListener {

public static final String TAG = "MainActivity";
private static final int EXIT_TIME = 2000;// 两次按返回键的间隔判断
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Sensor mMagneticField;
private LocationManager mLocationManager;
private String mLocationProvider;// 位置提供者名称,GPS设备还是网络
private float mCurrentDegree = 0f;
private float[] mAccelerometerValues = new float[3];
private float[] mMagneticFieldValues = new float[3];
private float[] mValues = new float[3];
private float[] mMatrix = new float[9];

private long firstExitTime = 0L;// 用来保存第一次按返回键的时间

private TextView mTvCoord;
private LinearLayout mLlLocation;
private TextView mTvAltitude;
private ImageView mIvCompass;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initService();
    findViews();
}

private void findViews() {
    mIvCompass = (ImageView) findViewById(R.id.iv_compass);
    mTvCoord = (TextView) findViewById(R.id.tv_coord);
    mTvAltitude = (TextView) findViewById(R.id.tv_altitude);
    mLlLocation = (LinearLayout) findViewById(R.id.ll_location);
    mLlLocation.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            initLocationService();
            updateLocationService();
        }
    });
}

private void initService() {
    initSensorService();

    initLocationService();
}

private void initSensorService() {
    mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    mMagneticField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}

private void initLocationService() {
    mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
    Criteria criteria = new Criteria();// 条件对象,即指定条件过滤获得LocationProvider
    criteria.setAccuracy(Criteria.ACCURACY_FINE);// 较高精度
    criteria.setAltitudeRequired(true);// 是否需要高度信息
    criteria.setBearingRequired(true);// 是否需要方向信息
    criteria.setCostAllowed(true);// 是否产生费用
    criteria.setPowerRequirement(Criteria.POWER_LOW);// 设置低电耗
    mLocationProvider = mLocationManager.getBestProvider(criteria, true);// 获取条件最好的Provider,若没有权限,mLocationProvider 为null
    Log.e(TAG, "mLocationProvider = " + mLocationProvider);
}


@Override
protected void onResume() {
    super.onResume();
    registerService();

}

private void registerService() {
    registerSensorService();

    updateLocationService();
}

private void registerSensorService() {
    mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
    mSensorManager.registerListener(this, mMagneticField, SensorManager.SENSOR_DELAY_NORMAL);
}

private void updateLocationService() {
    if (!checkLocationPermission()) {
        mTvCoord.setText(R.string.check_location_permission);
        return;
    }

    if (mLocationProvider != null) {
        updateLocation(mLocationManager.getLastKnownLocation(mLocationProvider));
        mLocationManager.requestLocationUpdates(mLocationProvider, 2000, 10, mLocationListener);// 2秒或者距离变化10米时更新一次地理位置
    } else {
        mTvCoord.setText(R.string.cannot_get_location);
    }
}


@Override
protected void onPause() {
    super.onPause();
    unregister();
}

private void unregister() {
    if (mSensorManager != null) {
        mSensorManager.unregisterListener(this);
    }

    if (mLocationManager != null) {
        if (!checkLocationPermission()) {
            return;
        }
        mLocationManager.removeUpdates(mLocationListener);
    }
}

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        mAccelerometerValues = event.values;
    }
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
        mMagneticFieldValues = event.values;
    }

    //调用getRotaionMatrix获得变换矩阵mMatrix[]
    SensorManager.getRotationMatrix(mMatrix, null, mAccelerometerValues, mMagneticFieldValues);
    SensorManager.getOrientation(mMatrix, mValues);
    //经过SensorManager.getOrientation(R, values);得到的values值为弧度
    //values[0]  :azimuth 方向角,但用(磁场+加速度)得到的数据范围是(-180~180),也就是说,0表示正北,90表示正东,180/-180表示正南,-90表示正西。
    // 而直接通过方向感应器数据范围是(0~359)360/0表示正北,90表示正东,180表示正南,270表示正西。
    float degree = (float) Math.toDegrees(mValues[0]);
    setImageAnimation(degree);
    mCurrentDegree = -degree;
}

// 设置指南针图片的动画效果
private void setImageAnimation(float degree) {
    RotateAnimation ra = new RotateAnimation(mCurrentDegree, -degree, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
            0.5f);
    ra.setDuration(200);
    ra.setFillAfter(true);
    mIvCompass.startAnimation(ra);
}

/**
 * 适配android 6.0 检查权限
 */
private boolean checkLocationPermission() {
    if (Build.VERSION.SDK_INT >= 23) {
        return (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
                PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) ==
                        PackageManager.PERMISSION_GRANTED);
    }

    return true;

}

/**
 * 更新位置信息
 */
private void updateLocation(Location location) {
    Log.e(TAG, "location = " + location);
    if (null == location) {
        mTvCoord.setText(getString(R.string.cannot_get_location));
        mTvAltitude.setVisibility(View.GONE);
    } else {
        mTvAltitude.setVisibility(View.VISIBLE);
        StringBuilder sb = new StringBuilder();
        double longitude = location.getLongitude();
        double latitude = location.getLatitude();
        double altitude = location.getAltitude();
        if (latitude >= 0.0f) {
            sb.append(getString(R.string.location_north, latitude));
        } else {
            sb.append(getString(R.string.location_south, (-1.0 * latitude)));
        }

        sb.append("    ");

        if (longitude >= 0.0f) {
            sb.append(getString(R.string.location_east, longitude));
        } else {
            sb.append(getString(R.string.location_west, (-1.0 * longitude)));
        }
        mTvCoord.setText(getString(R.string.correct_coord, sb.toString()));
        mTvAltitude.setText(getString(R.string.correct_altitude, altitude));
    }

}


LocationListener mLocationListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
        updateLocation(location);
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        if (status != LocationProvider.OUT_OF_SERVICE) {
            if (!checkLocationPermission()) {
                mTvCoord.setText(R.string.check_location_permission);
                return;
            }
            updateLocation(mLocationManager.getLastKnownLocation(mLocationProvider));
        } else {
            mTvCoord.setText(R.string.check_location_permission);
        }
    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {

    }
};


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

}

@Override
public void onBackPressed() {
    long curTime = System.currentTimeMillis();
    if (curTime - firstExitTime < EXIT_TIME) {
        finish();
    } else {
        Toast.makeText(this, R.string.exit_toast, Toast.LENGTH_SHORT).show();
        firstExitTime = curTime;
    }

}

}

完整代码地址:https://github.com/UserWang/Android-ConcisionCompass

参考文章:http://developer.android.com/intl/zh-cn/reference/android/hardware/SensorManager.html http://blog.csdn.net/microliang/article/details/15815091

⚠️ **GitHub.com Fallback** ⚠️