默说:现在在 Android 开发的时候需要获取用户的地理位置已经愈发的简单,各种地图 SDK 都提供精准的定位方法。不过如果你的需求是只需模糊定位到用户的城市,那样的话,系统 API 完全能满足你的需求,这时候再去集成一个地图 SDK 就感觉过重了。网上使用系统 API 进行定位的文章都比较早了,而且 Android 6.0 版本加入了危险权限的动态验证,所以基本没有个工具类能直接拿来就用的,那我只能再组装个轮子给大家使用了。没啥技术含量,大牛轻拍。
public void initLocation(Context context){
// 获取 LocationManager
mLocationManager = (LocationManager) context
.getSystemService(Context.LOCATION_SERVICE);
// 判断网络定位是否可用,可替换成 GPS 定位。
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
0, 0, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
//位置发生改变时回调该函数
location.getLatitude();//纬度
location.getLongitude();//经度
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
//状态改变回调
//provider:定位器名称(NetWork,Gps等)
//status: 3中状态,超出服务范围,临时不可用,正常可用
//extras: 包含定位器一些细节信息
}
@Override
public void onProviderEnabled(String provider) {
//定位开启回调
}
@Override
public void onProviderDisabled(String provider) {
//定位关闭回调
}
});
}
}
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
基本上,使用了这几个函数就能获取到经纬度了,是不是超级简单,这里使用网络进行定位是因为其定位速度比较快,且在室内也可以使用,虽然精度没有 gps 定位的高,但是我们的精度只到城市级别,所以这点误差完全可以接受。
LocationUtils类代码:
package cn.motalks.mtdc.utils;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
/**
* Created by MoLin on 16/9/25.
*/
public class LocationUtils {
private volatile static LocationUtils uniqueInstance;
private LocationHelper mLocationHelper;
private MyLocationListener myLocationListener;
private LocationManager mLocationManager;
private Context mContext;
private LocationUtils(Context context) {
mContext = context;
mLocationManager = (LocationManager) context
.getSystemService( Context.LOCATION_SERVICE );
}
//采用Double CheckLock(DCL)实现单例
public static LocationUtils getInstance(Context context) {
if (uniqueInstance == null) {
synchronized (LocationUtils.class) {
if (uniqueInstance == null) {
uniqueInstance = new LocationUtils( context );
}
}
}
return uniqueInstance;
}
/**
* 初始化位置信息
* @param locationHelper 传入位置回调接口
*/
public void initLocation(LocationHelper locationHelper) {
Location location = null;
mLocationHelper = locationHelper;
if (myLocationListener == null) {
myLocationListener = new MyLocationListener();
}
// 需要检查权限,否则编译报错,想抽取成方法都不行,还是会报错。只能这样重复 code 了。
if ( Build.VERSION.SDK_INT >= 23 &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return ;
}
if (mLocationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) {
location = mLocationManager.getLastKnownLocation( LocationManager.NETWORK_PROVIDER );
Log.e("MoLin", "LocationManager.NETWORK_PROVIDER");
if (location != null) {
locationHelper.UpdateLastLocation(location);
}
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
0, 0, myLocationListener);
} else {
Log.e("MoLin", "LocationManager.GPS_PROVIDER");
location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location != null) {
locationHelper.UpdateLastLocation(location);
}
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
1000, 50, myLocationListener);
}
}
private class MyLocationListener implements LocationListener {
//定位服务状态改变会触发该函数
@Override
public void onStatusChanged(String provider, int status,
Bundle extras) {
Log.e("MoLin", "onStatusChanged!");
if (mLocationHelper != null) {
mLocationHelper.UpdateStatus(provider, status, extras);
}
}
// 定位功能开启时会触发该函数
@Override
public void onProviderEnabled(String provider) {
Log.e("MoLin", "onProviderEnabled!" + provider);
}
// 定位功能关闭时会触发该函数
@Override
public void onProviderDisabled(String provider) {
Log.e("MoLin", "onProviderDisabled!" + provider);
}
// 当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发
@Override
public void onLocationChanged(Location location) {
Log.e("MoLin", "onLocationChanged!");
if (mLocationHelper != null) {
mLocationHelper.UpdateLocation(location);
}
}
}
// 移除定位监听
public void removeLocationUpdatesListener() {
// 需要检查权限,否则编译不过
if ( Build.VERSION.SDK_INT >= 23 &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return ;
}
if (mLocationManager != null) {
mLocationManager.removeUpdates(myLocationListener);
}
}
}
LocationUtils类的使用
这里的动态权限管理使用了都有米同学封装的 EasyPermissionsEx 类。(这个类下面会详细介绍)
package cn.motalks.mtdc.ui;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.GpsStatus;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import cn.motalks.mtdc.R;
import cn.motalks.mtdc.utils.EasyPermissionsEx;
import cn.motalks.mtdc.utils.LocationHelper;
import cn.motalks.mtdc.utils.LocationUtils;
/**
* Created by MoLin on 16/9/25.
*/
public class LocationTest2Activity extends Activity {
private TextView mLatitudeView;
private TextView mLongtitudeView;
private TextView mWeatherView;
private Button mButton;
private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
private static final int RC_SETTINGS_SCREEN = 2;
private String[] mNeedPermissionsList = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.location_text_activity);
initView();
super.onCreate(savedInstanceState);
}
private void initView() {
mLatitudeView = (TextView) findViewById(R.id.location_latitude);
mLongtitudeView = (TextView) findViewById(R.id.location_longtitude);
mWeatherView = (TextView) findViewById(R.id.weather_info_tv);
mButton = (Button) findViewById(R.id.location_button);
mButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
// 使用了 EasyPermissionsEx 类来管理动态权限配置
if (EasyPermissionsEx.hasPermissions(LocationTest2Activity.this, mNeedPermissionsList)) {
initLocation();
} else {
EasyPermissionsEx.requestPermissions(LocationTest2Activity.this, "需要定位权限来获取当地天气信息", PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION, mNeedPermissionsList);
}
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.e("MoLin", "已获取权限!");
initLocation();
} else {
if (EasyPermissionsEx.somePermissionPermanentlyDenied(this, mNeedPermissionsList)) {
EasyPermissionsEx.goSettings2Permissions(this, "需要定位权限来获取当地天气信息,但是该权限被禁止,你可以到设置中更改"
, "去设置", RC_SETTINGS_SCREEN);
}
}
}
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RC_SETTINGS_SCREEN) {
Toast.makeText(this, "settings", Toast.LENGTH_LONG).show();
}
}
private void initLocation() {
LocationUtils.getInstance(LocationTest2Activity.this).initLocation( new LocationHelper() {
@Override
public void UpdateLocation(Location location) {
Log.e("MoLin", "location.getLatitude():" + location.getLatitude());
mLatitudeView.setText(location.getLatitude() + "");
mLongtitudeView.setText(location.getLongitude() + "");
}
@Override
public void UpdateStatus(String provider, int status, Bundle extras) {
}
@Override
public void UpdateGPSStatus(GpsStatus pGpsStatus) {
}
@Override
public void UpdateLastLocation(Location location) {
Log.e("MoLin", "UpdateLastLocation_location.getLatitude():" + location.getLatitude());
mLatitudeView.setText(location.getLatitude() + "");
mLongtitudeView.setText(location.getLongitude() + "");
}
});
}
@Override
protected void onDestroy() {
// 在页面销毁时取消定位监听
LocationUtils.getInstance(LocationTest2Activity.this).removeLocationUpdatesListener();
super.onDestroy();
}
}
在Android 6.0及其以上版本上,系统在APP安装时授权所有普通权限,危险权限需要在使用时动态让用户授权。这使得Android的权限管理更加灵活,用户可以根据需要在设置应用中对应用的各个危险权限授予不同的权限。Android系统的权限管理不知道被多少人吐槽过,这一改进无疑是加分项。
下图是所有危险权限的列表,在使用时需要动态让用户授权。每个对应的危险权限都会对应一个权限组,若你申请了一个危险权限,得到用户允许后即可获得对应权限组的其他权限。例如你在申请了读短信的权限,用户点击了允许,那么你再申请获取写短信的权限时,系统会立即授予而不会再弹出对话框要求用户确认。
Google 之前在 github 上开源了一个 EasyPermissions ,但是会在解释为什么需要权限的时候弹出一个对话框阻塞了用户操作,并且在用户勾选”禁止后不再询问”后依然会弹出对话框请求权限,用户体验不太好。EasyPermissionsEx 一个国人基于 EasyPermissions 封装的类库,使用说明见《解读Android官方开发指导 - 运行时权限》。
https://github.com/apkcoder/AndroidLocationUtils
本文首发于 MoTalks.cn