Android指南针应用
目的:通過指南針應用來學習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() {@Overridepublic 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();// 條件對象,即指定條件過濾獲得LocationProvidercriteria.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 為nullLog.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() {@Overridepublic void onLocationChanged(Location location) {updateLocation(location);}@Overridepublic 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);}}@Overridepublic void onProviderEnabled(String provider) {}@Overridepublic 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
總結
以上是生活随笔為你收集整理的Android指南针应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 点击tab栏如何让tab置顶
- 下一篇: 网络运维系列:二级域名启用与配置