一、请求位置权限
为了保护用户隐私,使用位置信息服务的应用必须请求位置权限。
权限分类:
- 类别:
- 前台位置信息
- 后台位置信息
- 精确度:
- 确切位置
- 大致位置
前台位置信息
建议您声明location的前台服务类型。在 Android 10(API 级别 29)及更高版本中,您必须声明此前台服务类型。
当应用请求ACCESS_COARSE_LOCATION权限或ACCESS_FINE_LOCATION权限时,就是在声明需要获取前台位置信息。
后台位置信息
在 Android 10(API 级别 29)及更高版本中,您必须在应用的清单中声明ACCESS_BACKGROUND_LOCATION权限,以便请求在运行时于后台访问位置信息。在较低版本的 Android 系统中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。
大致位置
提供设备位置的估算值。如果此位置估算值来自LocationManagerService或FusedLocationProvider,则该估算值会精确到 3 平方公里(约 1.2 平方英里)以内。
声明了ACCESS_COARSE_LOCATION权限,但未声明ACCESS_FINE_LOCATION权限,您的应用就可以收到该精确度级别的位置信息。
确切位置
提供尽可能准确的设备位置估算值。如果位置估算值来自 LocationManagerService 或 FusedLocationProvider,则此估算值通常可以精确到 50 米(160 英尺)以内,有时甚至可以精确到几米(10 英尺)以内。
声明了 ACCESS_FINE_LOCATION 权限,您的应用就可以收到该精确度级别的位置信息。
二、定位使用
1、申请权限
2、定义工具类
public class LocationUtils {
private volatile static LocationUtils uniqueInstance;
private LocationManager locationManager;
private Context mContext;
private static ArrayList addressCallbacks;
private AddressCallback addressCallback;
public AddressCallback getAddressCallback() {
return addressCallback;
}
public void setAddressCallback(AddressCallback addressCallback) {
this.addressCallback = addressCallback;
if(isInit){
showLocation();
}else {
isInit = true;
}
}
private static Location location;
private boolean isInit = false;//是否加载过
private LocationUtils(Context context) {
mContext = context;
getLocation();
}
//采用Double CheckLock(DCL)实现单例
public static LocationUtils getInstance(Context context) {
if (uniqueInstance == null) {
synchronized (LocationUtils.class) {
if (uniqueInstance == null) {
addressCallbacks = new ArrayList<>();
uniqueInstance = new LocationUtils(context);
}
}
}
return uniqueInstance;
}
/**
* 添加回调事件
* @param addressCallback
*/
private void addAddressCallback(AddressCallback addressCallback){
addressCallbacks.add(addressCallback);
if(isInit){
showLocation();
}
}
/**
* 移除回调事件
* @param addressCallback
*/
public void removeAddressCallback(AddressCallback addressCallback){
if(addressCallbacks.contains(addressCallback)){
addressCallbacks.remove(addressCallback);
}
}
/**
* 清空回调事件
*/
public void cleareAddressCallback(){
removeLocationUpdatesListener();
addressCallbacks.clear();
}
private void getLocation() {
//1.获取位置管理器
locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
//添加用户权限申请判断
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
//2.获取位置提供器,GPS或是NetWork
// 获取所有可用的位置提供器
List providerList = locationManager.getProviders(true);
String locationProvider;
if (providerList.contains(LocationManager.GPS_PROVIDER)) {
//GPS 定位的精准度比较高,但是非常耗电。
System.out.println("=====GPS_PROVIDER=====");
locationProvider = LocationManager.GPS_PROVIDER;
} else if (providerList.contains(LocationManager.NETWORK_PROVIDER)) {//Google服务被墙不可用
//网络定位的精准度稍差,但耗电量比较少。
System.out.println("=====NETWORK_PROVIDER=====");
locationProvider = LocationManager.NETWORK_PROVIDER;
} else {
System.out.println("=====NO_PROVIDER=====");
// 当没有可用的位置提供器时,弹出Toast提示用户
Intent intent = new Intent();
intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
mContext.startActivity(intent);
return;
}
//3.获取上次的位置,一般第一次运行,此值为null
location = locationManager.getLastKnownLocation(locationProvider);
if (location != null) {
// 显示当前设备的位置信息
System.out.println("==显示当前设备的位置信息==");
showLocation();
} else {//当GPS信号弱没获取到位置的时候可从网络获取
System.out.println("==Google服务被墙的解决办法==");
getLngAndLatWithNetwork();//Google服务被墙的解决办法
}
// 监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace
//LocationManager 每隔 5 秒钟会检测一下位置的变化情况,当移动距离超过 10 米的时候,
// 就会调用 LocationListener 的 onLocationChanged() 方法,并把新的位置信息作为参数传入。
locationManager.requestLocationUpdates(locationProvider, 5000, 10, locationListener);
}
//获取经纬度
private void showLocation() {
if(location == null){
getLocation();
}else {
double latitude = location.getLatitude();//纬度
double longitude = location.getLongitude();//经度
// for(AddressCallback addressCallback:addressCallbacks){
// addressCallback.onGetLocation(latitude,longitude);
// }
if(addressCallback != null){
addressCallback.onGetLocation(latitude,longitude);
}
getAddress(latitude, longitude);
}
}
private void getAddress(double latitude, double longitude) {
//Geocoder通过经纬度获取具体信息
Geocoder gc = new Geocoder(mContext, Locale.getDefault());
try {
List locationList = gc.getFromLocation(latitude, longitude, 1);
if (locationList != null) {
Address address = locationList.get(0);
String countryName = address.getCountryName();//国家
String countryCode = address.getCountryCode();
String adminArea = address.getAdminArea();//省
String locality = address.getLocality();//市
String subLocality = address.getSubLocality();//区
String featureName = address.getFeatureName();//街道
for (int i = 0; address.getAddressLine(i) != null; i++) {
String addressLine = address.getAddressLine(i);
//街道名称:广东省深圳市罗湖区蔡屋围一街深圳瑞吉酒店
System.out.println("addressLine=====" + addressLine);
}
if(addressCallback != null){
addressCallback.onGetAddress(address);
}
// for(AddressCallback addressCallback:addressCallbacks){
// addressCallback.onGetAddress(address);
// }
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void removeLocationUpdatesListener() {
if (locationManager != null) {
uniqueInstance = null;
locationManager.removeUpdates(locationListener);
}
}
private LocationListener locationListener = new LocationListener() {
// Provider的状态在可用、暂时不可用和无服务三个状态直接切换时触发此函数
@Override
public void onStatusChanged(String provider, int status, Bundle arg2) {
}
// Provider被enable时触发此函数,比如GPS被打开
@Override
public void onProviderEnabled(String provider) {
}
// Provider被disable时触发此函数,比如GPS被关闭
@Override
public void onProviderDisabled(String provider) {
}
//当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发
@Override
public void onLocationChanged(Location loc) {
System.out.println("==onLocationChanged==");
// location = loc;
// showLocation();
}
};
//从网络获取经纬度
private void getLngAndLatWithNetwork() {
//添加用户权限申请判断
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 5000, 10, locationListener);
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
showLocation();
}
public interface AddressCallback{
void onGetAddress(Address address);
void onGetLocation(double lat,double lng);
}
}
3、开始定位
//注意6.0及以上版本需要在申请完权限后调用方法
LocationUtils.getInstance(this).setAddressCallback(new LocationUtils.AddressCallback() {
@Override
public void onGetAddress(Address address) {
String countryName = address.getCountryName();//国家
String adminArea = address.getAdminArea();//省
String locality = address.getLocality();//市
String subLocality = address.getSubLocality();//区
String featureName = address.getFeatureName();//街道
LogUtils.eTag("定位地址",countryName,adminArea,locality,subLocality,featureName);
}
@Override
public void onGetLocation(double lat, double lng) {
LogUtils.eTag("定位经纬度",lat,lng);
}
});
注意:
某些版本的手机在使用Geocoder进行地址转换的时候会出现如下异常:
java.io.IOException: grpc failed
原因为:the service is not available 服务不可用 即设备不支持Geocoder
这时可以使用Google的API接口进行解析,API接口如下:
https://maps.googleapis.com/maps/api/geocode/json?latlng=37.422,-122.084&language=zh-CN&sensor=false&key=YOUR_API_KEY
这里的key需要去GoogleMap开发平台去申请
三、定位功能节省电量
1、理解电池电量消耗
位置收集和电池电量消耗在以下几个方面直接相关:
精确度:位置数据的精度。一般而言,精确度越高,电池电量消耗越大。
频率:位置的计算频率。位置的计算频率越高,电池电量消耗越大。
延迟时间:位置数据的交付速度。延迟越低,电池电量消耗越大。
1.精确度
您可使用setPriority()方法指定位置的精确度,传递以下任意值作为参数:
- PRIORITY_HIGH_ACCURACY
提供最高精确度,计算过程使用尽可能多的输入(启用 GPS、WLAN 和蜂窝网络,并使用各种各样的传感器会产生巨大的电池电量消耗。 - PRIORITY_BALANCED_POWER_ACCURACY
提供准确的位置,同时进行了耗电优化。极少使用 GPS。通常使用 WLAN 和蜂窝网络信息的组合来计算设备位置。 - PRIORITY_LOW_POWER
主要依赖移动电话基站,避免 GPS 和 WLAN 输入,从而以最小的电池电量消耗提供粗略的(城市级)精确度。 - PRIORITY_NO_POWER
被动地从已计算出位置的其他应用接收位置。
大多数应用的定位需求可以通过 Balanced Power 或 Low Power 选项来满足。应仅针对在前台运行且要求实时位置更新的应用(例如地图应用)使用 High Accuracy 选项。
2.频率
您可以使用以下两种方法指定位置更新频率:
- 使用setinterval()方法指定应用计算位置的时间间隔。
- 使用setFastestInterval()指定其他应用已计算出的位置发送至您的应用的时间间隔。
您应使用 setInterval()传递最大值。这对于后台位置收集而言尤其如此,因为后台位置收集通常会导致不受欢迎的电池电量消耗。应对前台用例使用几秒钟的时间间隔。Android 8.0 引入的后台位置更新限制将会执行这些策略,但您的应用也应努力在 Android 7.0 或更低版本的设备上执行它们。
3.延迟时间
您可使用setMaxWaitTime()方法指定延迟时间,传递的值通常比 setInterval()方法中指定的时间间隔大几倍。此设置将延迟位置传递,而且多个位置更新可能分批传递。这两个变化有助于减少电池电量消耗。
如果您的应用无需即时位置更新,则您应将向 setMaxWaitTime()方法传递最大值,通过牺牲延迟时间来获得更多数据和更高的电池效率。
当使用地理围栏时,应用应向setNotificationResponsiveness()方法传递一个较大值来节省耗电量。建议设置五分钟或更大的值。
2、定位最佳做法:
1.移除位置更新
造成不必要的电池电量消耗的一个常见原因是,当不再需要位置更新时,没有移除它们。
例如,当某个 Activity 的onStart()或onResume()生命周期方法中包含requestlocationUpdates()调用,但在onPause()或 onStop()生命周期方法中却没有相应的removeLocationUpdates()调用时,就会发生这种情况。
2.设置超时
为了防止电池电量消耗,应设置一个停止位置更新的合理超时。通过设置超时,可确保更新不会无限期地继续,并且在请求更新后未移除更新的情况下(例如由于代码错误),对应用起到保护作用。
3.批处理请求
对于所有非前台用例,将多个请求一起进行批处理。您可使用setInterval()方法指定计算位置的时间间隔。然后使用setMaxWaitTime()方法设置位置传递给应用的时间间隔。传递给 setMaxWaitTime()方法的值应是传递给 setInterval()方法的值的倍数。
例如,请考虑以下位置请求:
val request = LocationRequest()
request.setInterval(10 * 60 * 1000)
request.setMaxWaitTime(60 * 60 * 1000)
4.使用被动位置更新
val request = LocationRequest()
request.setInterval(15 * 60 * 1000)
request.setFastestInterval(2 * 60 * 1000)
您的应用大约每 15 分钟计算一次位置。如果其他应用请求位置,则您的应用可在最多两分钟后获得这些信息。
参考:https://www.jianshu.com/p/0d52ca1cb85b