Android 系统提供了地理位置服务相关的API,方便了开发者去获得当前地理位置。用户可以通过GPS接收器接受地理位置。也可以通过network(wifi或者基站)来获得当前的定位。
知识点
一、梳理
1、谁提供的定位信息?
安卓的位置信息由“位置提供者”提供,在安卓中有三种位置提供者即:gps、network、passive。前两者我们或许还可以理解但是这个passive是个啥东西?客官别急,,,,这就开始分析!
1、gps:gps提供者就是使用手机内置的gps硬件接收器接受精确的位置信息。
2、network:就是使用网络作为位置提供者,这里网络可以为wifi、基站。因为wifi、基站也是可以提供粗略的定位的。
3、psssive:这个提供者有点特别。从这个单词来看是“被动的”意思。其实这个就是个被动接收者。怎么理解呢?你可以这样理解:当其他的app、或者服务(service)请求到位置信息时,这个接收者可以接受到他们请求到的位置信息。看到这里我们就明白了。也可以知道使用这个提供者时获取到的位置并不一定是正确的。
2、安卓Location框架的架构
如下图(图片来自网络,如有侵权联系作者删除,,,,)开发者主要使用LocationManager来获得位置信息。然而位置信息的获得是个跨进程通信的过程。真正的位置信息的获取、位置提供者的查询、位置的实时监听等都是由系统的LocationManagerService来负责接手的。
1、如果用户是使用了gps定位方式,那么LocationManagerService就委托给GpsLocationProvider去获得精确位置信息。
2、如果用户使用的network 定位方式,LocationManagerService就委托给第三方实现去获得粗略的位置信息。
ps:google的api中并未实现netWorkProvider的具体逻辑,这个功能交给了第三方服务去完成。国内手机一般都集成了百度地图、或者高德地图。这两个第三方的app中就有一个service,就是netWorkProvider的具体实现。这里又涉及到了 安卓系统位置服务于第三方app的位置服务跨进程通信。
二、常用类
1、LocationManager
位置管理者,应用层(开发者)主要使用这个类和系统的位置服务进行通信。
(1)获取方式
LocationManager locationManager =
(LocationManager) getSystemService(LOCATION_SERVICE);
(2)常用方法或字段
// 常用的静态常量
GPS_PROVIDER // gps 提供者
NETWORK_PROVIDER // network提供者
PASSIVE_PROVIDER // passive提供者(需要)
// 返回手机支持的所有提供者(上述三种:gps、network、passive)
public List<String> getAllProviders()
//根据筛选条件返回最符合条件的位置提供者,一般筛选最符合条件能用的提供者。
public String getBestProvider(Criteria criteria, boolean enabledOnly)
// 一般参数为true,返回能用的提供者集合
List<String> getProviders(boolean enabledOnly)
// 获得最近一次使用过的Location信息对象,最近没有使用过时返回null。一般此方法不用。
public Location getLastKnownLocation(String provider);
// 注册,需要位置的实时更新
//provider:位置提供者,表名位置数据由那种提供者提供。
//minTime:最小时间间隔,位置刷新期间的。多久刷新一次。
//minDistance:最小的位置,位置刷新期间。超过这个位置也触发刷新。
// LocationListener :位置监听
void requestLocationUpdates(String provider, long minTime, float minDistance,
LocationListener listener)
// 解注册
public void removeUpdates(LocationListener listener)
2、Location:
位置类,内部封装了经纬度、海拔之类的位置信息。
location.getLongitude();//经度
location.getLatitude();// 纬度
location.getProvider();//位置提供者
3、Geocoder:将地址转换为相应的地理坐标。这一过程称为地理编码。或者,您可以将地理位置转换为地址。地址查询功能也称为逆向地理编码。
maxResults:返回地址的数目取1~5之间
因为同一经纬度可能对应多个地址
public List<Address> getFromLocation(double latitude, double longitude, int maxResults)
1、可见提供了三个方法,常用的为第一个。根据经纬度获取Address集合。
2、Geocoder 请求的是一个后台服务,但是该服务不包括在标准android framework中。因此如果当前设备不包含location services,则Geocoder返回的地址或者经纬度为空。当然你可以使用 Geocoder#isPresent()方法来判断当前设备是否包含地理位置服务。由于国内使用不了Google Services服务,因此一般的手机厂商都会在自己的手机内内置百度地图服务,或者高德地图服务来替代Google Services服务。
4、Address:详细的地理位置
一般通过如下api我们即可获得 XXX市XXX区XXX街区XXX小区信息。
三、权限&api请求流程
1、动态权限
<!-- GPS定位权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- wifi/基站定位权限-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
安卓6.0开始,系统对危险权限添加了动态权限申请的功能。上述的位置权限在6.0或者以上的手机中需要动态申请下。
参考文章:安卓动态权限
2、大致使用流程图解
四 、栗子及其Criteria类的补充
1、Criteria
String bestProvider = locationManager.getBestProvider(new Criteria(), true);
细心的你可能会留意到,上述api简介中,获得provider时我们还会用到Criteria类。其实这个类就是筛选合适的provider的。常见的筛选条件如下:
public void setAccuracy(int accuracy):位置解析精确度,参数Criteria.ACCURACY_FINE:表示高精确度。Criteria.ACCURACY_COARSE:表示模糊精确度。
public void setAltitudeRequired(boolean altitudeRequired ):是否要求海拔信息。
public void setBearingRequired(boolean bearingRequired):是否要求方向信息。
public void setCostAllowed(boolean costAllowed):是否允许收费。
public void setSpeedRequired(boolean speedRequired):是否要求速度信息。
public void setBearingRequired(boolean bearingRequired):是否要求方向信息。
public void setPowerRequirement(int level):设置电池消耗要求,参数 Criteria. NO_REQUIREMENT, Criteria. POWER_LOW, Criteria. POWER_MEDIUM, Criteria. POWER_HIGH。分别表示 :无、低、中、高,。
public void setBearingAccuracy(int accuracy):设置方向的精准度。参数 Criteria.NO_REQUIREMENT, Criteria.ACCURACY_LOW, Criteria.ACCURACY_HIGH。分别表示:无,低,高。
public void setSpeedAccuracy(int accuracy):设置速度的精准度。参数同上。
public void setHorizontalAccuracy(int accuracy):设置水平方向的精准度。参数同上。
public void setVerticalAccuracy(int accuracy):设置垂直方向的精准度。参数同上。
Criteria类该怎么使用?代码示例如下:
criteria.setAccuracy(Criteria.ACCURACY_FINE);//设置定位精准度
criteria.setAltitudeRequired(false);//是否要求海拔
criteria.setBearingRequired(true);//是否要求方向
criteria.setCostAllowed(true);//是否要求收费
criteria.setSpeedRequired(true);//是否要求速度
criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);//设置电池耗电要求
criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);//设置方向精确度
criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);//设置速度精确度
criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);//设置水平方向精确度
criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);//设置垂直方向精确度
//返回满足条件的,当前设备可用的location provider,当第二个参数为false时,返回当前设备所有provider中最符合条件的那个provider(但是不一定可用)。
String mProvider = mLocationManager.getBestProvider(criteria,true);
2、栗子
public class MainActivity extends AppCompatActivity {
private TextView tvLocation;
private TextView tvAddress;
private Geocoder geocoder;
private List<Address> addressList;
private StringBuilder sb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUi();
initData();
}
private void initData() {
// 获取经纬度坐标
// 1 获取位置管理者对象
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// 2 通过lm获得经纬调度坐标
// (参数: provider(定位方式 提供者 通过 LocationManager静态调用),
// minTime(获取经纬度间隔的最小时间 时时刻刻获得传参数0),
// minDistance(移动的最小间距 时时刻刻传0),LocationListener(监听))
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// 获取经纬度主要方法
double latitude = location.getLatitude();
double longitude = location.getLongitude();
tvLocation.setText("latitude"+latitude+" "+"longitude"+longitude);
sb = new StringBuilder();
geocoder = new Geocoder(MainActivity.this);
addressList = new ArrayList<Address>();
try {
// 返回集合对象泛型address
addressList= geocoder.getFromLocation(latitude,longitude,1);
if (addressList.size() > 0) {
Address address = addressList.get(0);
for (int i = 0; i < address.getMaxAddressLineIndex(); i++) {
sb.append(address.getAddressLine(i)).append("\n");
}
sb.append(address.getFeatureName());//周边地址
}
tvAddress.setText("当前位置"+sb.toString());
;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
//状态发生改变监听
}
@Override
public void onProviderEnabled(String s) {
// ProviderEnabled
}
@Override
public void onProviderDisabled(String s) {
// ProviderDisabled
}
});
}
private void initUi() {
tvLocation = (TextView) findViewById(R.id.tv_location);
tvAddress = (TextView) findViewById(R.id.tv_address);
}
}
贴图如下
五、扩展及其采坑
1、扩展
安卓手机从屏幕上端下滑,拉出功能栏,点击GPS图标开启GPS,这时你的手机设置->位置信息模式 会选中“高精确度”定位方式,当你点击GPS图标关闭时,会使用默认的“低耗电量”(如下图)
2、采坑network 提供者下onLocationChange不回调,拿不到Location对象。
1、场景:
安卓TV开发的同学或许会碰到这种情况,TV真机上onLocationChange没有回调。拿不到Location对象。
2、解释:
其实TV不支持gps硬件功能,如果想采用定位那只有使用network提供者了,但是通过上述的架构图我们也可以知道。提供者为network时,位置的获取需要第三方服务。由于TV上一般都没有地图类app所以第三方就是空实现了。location对象就拿不到了。
3、验证依据:
- 安卓系统开机时可以打印系统log(使用adb logcat 命令)
- LocationManagerService的初始化在安卓系统类的SystemServer的startOtherService方法中完成。这个操作在安卓系统启动时就进行。
4、进行验证:重启TV打印系统log(如下图找到你指定输出的log.txt文件),结果搜索LocationManagerService关键字发现“no network location provider found”,默认使用的gms(google map service)也没找到。
5、一种解决方案:在tv上集成三方sdk比如高德地图。
参考:
1、Android网络定位源码分析
2、https://developer.android.google.cn/reference/android/location/LocationManager?hl=en