Android 系统提供了地理位置服务相关的API方便开发者去获得当前地理位置。在android framework层的android.loaction包下面主要提供了如下两个类来帮助开发者来获取地理位置信息。
今天我们通过简单例子来详细说明使用以上两个类来帮助开发者获取地理位置信息。
//网络权限
<uses-permission android:name="android.permission.INTERNET"/>
//模糊定位权限:一般用于网络定位
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
//精确定位权限:一般用于gps定位
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
该类提供了访问地理位置的服务,可以获取上一次最新的地理位置信息,也可以注册监听事件来周期性的获得设备更新的地理位置信息。在获取地理位置信息时我们必须了解一下两个知识点:
位置信息的提供者,android系统一般提供三种方式来获取地理位置信息。分别如下:
LocationManager类中提供了以下几种方法来获得地理位置提供者。
public List<String> getAllProviders();
以上方法返回当前设备所有地理位置提供者。
public List<String> getProviders(boolean enabledOnly);
以上方法当参数为true时,返回的时当前设备可使用的位置提供者;当参数为false时和上面那个方法一样,返回所有的位置提供者。
public String getBestProvider(Criteria criteria, boolean enabledOnly);
以上方法返回当前设备最符合指定条件的位置提供者,第一个参数criteria用于指定条件,第二个参数表示是否返回当前设备可用的位置提供者。现在来分析这两个参数。
public List<String> getProviders(Criteria criteria, boolean enabledOnly);
以上方法也是返回当前符合条件的所有可用的provider。
该类用于指定帅选最符合条件的地理位置提供者,根据Cirteria指定的条件,设备会自动选择哪种location provider。Criteria类中提供如下方法来指定条件。
Criteria类该怎么使用?代码示例如下:
Criteria criteria = new 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);
LocationManager类中提供了如下方法来获得最新一次更新的地理位置信息。
public Location getLastKnownLocation(String provider);
以上方法可以传入相应的 location provider来获得当前地理位置信息。代码示例如下:
LocationManager mLocationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
mLocationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
getLastKnownLocation()方法只能一次性的获得当前最新的地理位置,但是它不能实时的监听地理位置的变化情况。Android给开发者提供了一个接口监听类LocationListener来实时监听当前设备的地理位置变化情况。
LocationListener用于监听当地理位置有改变时来接收通知的接口类。在使用该监听之前您必须要用LocationManager类中的如下方法来注册该监听事件。
public void requestLocationUpdates(String provider, long minTime, float minDistance,
LocationListener listener)
以上注册监听方法,参数一:位置提供者;参数二:位置更新最短时间(单位ms);参数三:位置更新最短距离(单位m);参数四:LocationListener监听器对象。
LocationListener接口类中有如下方法:
public interface LocationListener {
//当位置改变时调用.
void onLocationChanged(Location location);
//当location provider的状态改变时,该方法被调用。状态有三种:
//LocationProvider#OUT_OF_SERVICE:无服务
//LocationProvider#TEMPORARILY_UNAVAILABLE:provider不可用
//LocationProvider#AVAILABLE:provider可用
void onStatusChanged(String provider, int status, Bundle extras);
//当provider可用时调用,比如gps可用时就会调用该方法。
void onProviderEnabled(String provider);
//当provider不可用时调用该方法。比如gps未打开,gps不可用就会调用该方法。
void onProviderDisabled(String provider);
}
接下来通过 一个demo来了解 LocationListener的使用:
package com.xjp.locationservice;
import android.content.Context;
import android.location.*;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
/** * Created by xjp on 2016/2/21. */
public class LocationDemo {
private Context mContext;
private MyLocationListener listener;
private final static String TAG = "LocationDemo";
private android.location.LocationManager locationManager;
public LocationDemo(Context context) {
this.mContext = context;
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
listener = new MyLocationListener();
}
//开始请求地理位置更是
public void startRequestLocationUpdates(){
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1f, listener);
}
//停止地理位置更新
public void stopRequestLocationUpdates(){
locationManager.removeUpdates(listener);
}
public Location getCurrentLocation() {
return listener.current();
}
private class MyLocationListener implements LocationListener {
Location mLastLocation;
boolean mValid = false;
@Override
public void onLocationChanged(Location newLocation) {
if (newLocation.getLatitude() == 0.0
&& newLocation.getLongitude() == 0.0) {
// Hack to filter out 0.0,0.0 locations
return;
}
if (!mValid) {
Log.d(TAG, "Got first location.");
}
mLastLocation.set(newLocation);
Log.d(TAG, "the newLocation is " + newLocation.getLongitude() + "x" + newLocation.getLatitude());
mValid = true;
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
switch (status) {
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE: {
mValid = false;
break;
}
}
}
@Override
public void onProviderEnabled(String provider) {
Log.d(TAG, " support current " + provider);
}
@Override
public void onProviderDisabled(String provider) {
Log.d(TAG, "no support current " + provider);
mValid = false;
}
//获得当前地理位置
public Location current() {
return mValid ? mLastLocation : null;
}
}
}
一般情况我们会使用一种provider来获得地理位置信息,但为了高效性,我们可以结合使用两种provider来获得地理位置信息。当在室内时自动使用NETWORK_PROVIDER,室外无网络情况下使用GPS_PROVIDER。那么gps和network怎么结合使用呢?其实很简单,参照上一个例子,同时注册两个provider监听即可实现。代码示例如下:
package com.xjp.locationservice;
import android.content.Context;
import android.location.*;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
/** * Created by xjp on 2016/2/21. */
public class LocationDemo {
private Context mContext;
private MyLocationListener listener;
private final static String TAG = "LocationDemo";
private android.location.LocationManager locationManager;
private MyLocationListener listeners[] = {
new MyLocationListener(),
new MyLocationListener()
};
public LocationDemo(Context context) {
this.mContext = context;
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
listener = new MyLocationListener();
}
public void startRequestLocationUpdates() {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1f, listeners[0]);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1F, listeners[1]);
}
public void stopRequestLocationUpdates() {
locationManager.removeUpdates(listeners[0]);
locationManager.removeUpdates(listeners[1]);
}
public Location getCurrentLocation() {
// go in best to worst order
for (int i = 0; i < listeners.length; i++) {
Location l = listeners[i].current();
if (l != null) return l;
}
Log.d(TAG, "No location received yet.");
return null;
}
private class MyLocationListener implements LocationListener {
Location mLastLocation;
boolean mValid = false;
@Override
public void onLocationChanged(Location newLocation) {
if (newLocation.getLatitude() == 0.0
&& newLocation.getLongitude() == 0.0) {
// Hack to filter out 0.0,0.0 locations
return;
}
if (!mValid) {
Log.d(TAG, "Got first location.");
}
mLastLocation.set(newLocation);
Log.d(TAG, "the newLocation is " + newLocation.getLongitude() + "x" + newLocation.getLatitude());
mValid = true;
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
switch (status) {
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE: {
mValid = false;
break;
}
}
}
@Override
public void onProviderEnabled(String provider) {
Log.d(TAG, " support current " + provider);
}
@Override
public void onProviderDisabled(String provider) {
Log.d(TAG, "no support current " + provider);
mValid = false;
}
public Location current() {
return mValid ? mLastLocation : null;
}
}
}
如此,即可实现两种provider结合使用,来提高获得地理位置的效率。
该类用于获取地理位置的前向编码和反向编码,前向编码是根据地址获取经纬度;反向编码是根据经纬度获取对应的详细地址。Geocoder 请求的是一个后台服务,但是该服务不包括在标准android framework中。因此如果当前设备不包含location services,则Geocoder返回的地址或者经纬度为空。当然你可以使用 Geocoder#isPresent()方法来判断当前设备是否包含地理位置服务。由于国内使用不了Google Services服务,因此一般的手机厂商都会在自己的手机内内置百度地图服务,或者高德地图服务来替代Google Services服务。
Geocoder类提供一下几个方法:
public static boolean isPresent()
判断当前设备是否内置了地理位置服务。返回true表示Geocoder地理编码可以使用,否则不可使用。国内一般用不了Google services,所以一般使用百度地图或者高德地图来代替。
public List<Address> getFromLocation(double latitude, double longitude, int maxResults)
以上方法根据经纬度返回对应的地理位置信息。参数一:经度;参数二:维度;参数三:返回地址的数目(由于同一经纬度可能对于多个地址,该参数取1~5之间)。
public List<Address> getFromLocationName(String locationName, int maxResults)
以上方法根据具体地址来获取相应的地理位置信息。参数一:地址;参数二:返回地址的数目(取1~5之间)。
代码示例如下:
private String getAddress(Location location) throws IOException {
Geocoder geocoder = new Geocoder(this);
boolean falg = geocoder.isPresent();
Log.e("xjp","the falg is " + falg);
StringBuilder stringBuilder = new StringBuilder();
try {
//根据经纬度获取地理位置信息
// List<Address> addresses = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
//根据地址获取地理位置信息
List<Address> addresses = geocoder.getFromLocationName( "广东省珠海市香洲区沿河路321号", 1);
if (addresses.size() > 0) {
Address address = addresses.get(0);
for (int i = 0; i < address.getMaxAddressLineIndex(); i++) {
stringBuilder.append(address.getAddressLine(i)).append("\n");
}
stringBuilder.append(address.getCountryName()).append("_");//国家
stringBuilder.append(address.getFeatureName()).append("_");//周边地址
stringBuilder.append(address.getLocality()).append("_");//市
stringBuilder.append(address.getPostalCode()).append("_");
stringBuilder.append(address.getCountryCode()).append("_");//国家编码
stringBuilder.append(address.getAdminArea()).append("_");//省份
stringBuilder.append(address.getSubAdminArea()).append("_");
stringBuilder.append(address.getThoroughfare()).append("_");//道路
stringBuilder.append(address.getSubLocality()).append("_");//香洲区
stringBuilder.append(address.getLatitude()).append("_");//经度
stringBuilder.append(address.getLongitude());//维度
System.out.println(stringBuilder.toString());
}
} catch (IOException e) {
// TODO Auto-generated catch block
Toast.makeText(this, "报错", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
return stringBuilder.toString();
}
以上代码可以看出,其返回的地理位置信息是一个集合Address,其中Address类中包含了各种地理位置信息,包括经纬度,国家,城市,地区,街道,国家编码,城市编码等等。
注意:Geocoder获取地理位置信息是一个后台的耗时操作,为了不阻塞主线程,强力建议在使用Geocoder获取地理位置信息时采用异步线程的方式来请求服务,这样不会造成主线程的阻塞。