Android 定位和地图

概述:

使用设备上的传感器可以为APP增加丰富的定位和运动能力, 从GPS或者网络定位到加速度计, 陀螺仪, 温度, 气压计等.

定位和地图:

注意, 这里介绍的是Android framework中android.location包中的定位API. Google Location Services API, 是Google Play提供的服务, 提供了更加强大高级的框架可以自动计算任务比如定位提供选择和电源管理. 定位服务也提供了新的功能, 比如framework API中不支持的activity识别. 使用framework API的开发者以及那些只是向APP中添加位置感知的开发者, 应该认真考虑使用Location Services API. 更多关于Location ServiceAPI的信息可以参考GoogleLocation Services for Android.

定位和基于地图的APP为移动设备提供了丰富的体验. 我们可以在APP内使用android.location包和Google Maps Android API创建这些功能, 下面的小节将会简单介绍如何使用这些功能.

定位服务(LocationServices):

Android通过android.location为APP访问定位服务. 定位框架的核心组件是LocationManager系统服务, 它提供API来确定底层设备的位置和方向(如果可用的话).

跟其它的系统服务一样, 我们不能直接实例化一个LocationManager. 而是通过getSystemService(Context.LOCATION_SERVICE)方法来从系统请求一个实例.该方法返回一个LocationManager实例的句柄. 一旦我们的APP拥有了一个LocationManager, 就可以做这三件事:

l  查询所有的最后已知的用户位置的LocationProvider的列表.

l  注册/注销从locationprovider定期更新用户当前位置(被标准或者名字指定).

l  为一个给定的intent注册/注销一个给定经纬度的邻近范围.

更多信息将在下面介绍.

GoogleMaps Android API:

通过GoogleMaps Android API, 我们可以为APP添加基于Google Maps数据的地图. 该API会自动处理对Google Maps服务器的访问, 数据下载, 地图显示和地图上的点击手势. 我们可以使用API来增加标记, 多边形和覆盖图, 并更改特定地图区域用户的视角.

Google Maps Android API中的关键类是MapView.一个MapView可以通过从Google Maps服务器获得的数据显示一个地图. 当MapView有一个焦点的时候, 它将会捕捉按键和点击手势来自动平移和扩展地图, 包括处理额外的地图片段的网络需求. 它还提供了所有用户控制地图用到的UI元素. 我们的APP还可以使用MapView类方法来控制地图编程并绘制一些重叠区.

Google Maps Android API不包括在Android平台中, 但是可以在任何GooglePlay Store中的APP上运行(Android 2.2及以上版本). 想要集成Google Maps到APP中, 我们需要安装Google Play服务库到Android SDK. 更多信息可以参考Google Playservice.

定位策略:

了解用户的位置可以让我们的APP更加聪明和人性化. 当开发一个可定位的APP的时候, 我们可以利用GPS和Android的Network Location Provider来获得用户的位置. 尽管GPS是最准确的, 但是它只能工作在户外, 而且非常费电, 也没有用户期待的那么快的反应速度. Android的Network Location Provider使用手机信号塔和WiFi信号定位, 可以在室内和户外获取到位置信息, 响应迅速, 而且也消耗更少的电量. 如果在APP中想要获取用户的位置, 我们可以合作使用GPS和Network Location Provider, 也可以只使用其中一个.

定位要面临的挑战:

从一个移动设备获取用户位置可以很复杂. 有几个原因导致读取位置可能包含错误和误差. 这些问题有:

1.      位置源众多: GPS, 通信塔ID, WiFi都可以提供用户位置的线索. 确定要用哪个源需要权衡多种因素, 比如精确度, 速度和电池效率.

2.      用户移动: 因为用户的座标会变, 我们必须每隔一段时间就刷新用户的位置.

3.      变化的精确度: 每个源提供的精确度都可能不一致. 一个10秒钟之前从一个源取得的位置可能比最新的从其它源获取的位置(或者同一个源)更准确.

这些问题导致想要获取一个靠谱的用户位置变得很复杂. 该文档将会提供一些信息来帮助我们面对这些挑战. 它也提供了我们可以用来使APP变得更加流畅和精确的点子供我们参考.

请求位置更新:

在Android上获取用户位置信息的原理是回调方式. 想要获取用户位置需要使用LocationManager的requestLocationUpdates(), 并传给它一个LocationListener作为参数. 我们的LocationListener必须实现一些回调方法给LocationManager在用户位置发生变化或者服务状态发生变化的时候调用. 比如下面这段代码演示了如何定义一个LocationListener并请求更新位置信息:

// Acquire a reference to the system Location Manager
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

// Define alistener that responds to location updates
LocationListener locationListener = new LocationListener() {
    public void onLocationChanged(Location location) {
      // Called when a new location is found by the network location provider.
      makeUseOfNewLocation(location);
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {}

    public void onProviderEnabled(String provider) {}

    public void onProviderDisabled(String provider) {}
  };

// Register thelistener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);

requestLocationUpdates()的第一个参数是要使用的locationprovider的类型(在这里使用的是Network Location Provider, 表示手机信号塔和WiFi). 我们可以用第二个和第三个参数来控制监听器接收更新的频率 – 第二个参数是两次提示的最小时间间隔, 第三个参数是两次提示的最小改变距离– 如果两个都被设置为0的话, 则表示用可用的最快频率. 最后一个参数是LocationListener, 用于接收回调方法.

想要通过GPS Provider请求位置更新, 需要使用GPS_PROVIDER替代NETWORK_PROVIDER. 我们还可以通过调用requestLocationUpdates()两次来同时请求GPS和NetworkLocation Provider, 一次用NETWORK_PROVIDER, 一次用GPS_PROVIDER.

请求用户权限:

为了从NETWORK_PROVIDER或者GPS_PROVIDER接收位置更新, 我们必须在manifest中分别声明ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION权限来获取用户权限, 栗子:

<manifest ... >
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
</manifest>

如果没有这些权限的话, APP会在请求位置更新的时候失败. 如果我们一起使用了NETWORK_PROVIDER和GPS_PROVIDER, 那么只需要请求ACCESS_FINE_LOCATION权限, 因为它包含这两种provider的权限(ACCESS_COARSE_LOCATION则只包括NETWORK_PROVIDER的权限).

定义一个最佳性能模型:

基于位置的APP现在十分的常见, 但是由于要获取最佳的精确度, 用户位置变化, 还有多种获取位置的方法, 以及考虑电池消耗, 这些让获取用户位置变得复杂起来. 想要获得最佳的用户位置并保持电池的电量, 我们必须定义一个统一的模型来指定APP如何获取用户位置. 这个模型包括什么时候启动和停止监听位置变化和什么时候使用缓存位置数据.

获取用户位置的流程:

这是典型的获取用户位置的流程:

1.      启动APP.

2.      过会儿之后启动监听器来从Location provider获取更新.

3.      通过过滤掉较新但是不精确的数据保留一个”当前最佳的预计位置(current best estimate)”.

4.      停止监听位置更新.

5.      使用最后最佳的预计位置.

下图在一个时间轴上用可视化的时段展示了这种模型, 它包含了这段时间监听到的位置更新事件:


这个图片展示了很多我们在使用定位APP的时候需要做的决定.

决定什么时候启动位置更新监听:

我们可能想要在APP刚启动的时候就启动位置更新的监听器, 或者仅在用户激活功能之后才启动. 长时间的监听会消耗大量的电量, 但是短时间的监听又不能保证精确度. 我们可以通过requestLocationUpdates()方法启动监听:

String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or, use GPSlocation data:
// StringlocationProvider = LocationManager.GPS_PROVIDER;

locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);

使用最后一次已知位置获取快速修正:

通常获取第一次位置修正都会消耗很多时间. 在取得更加精确的位置信息之前, 可以使用getLastKnownLocation(String)方法获得一个缓存的位置信息:

String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or useLocationManager.GPS_PROVIDER

Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);

决定何时停止监听:

决定停止监听的逻辑根据APP的需求可能很简单也可能很复杂. 同时还要记得定位会消耗很多电量, 应该在取得需要的信息之后就停止监听, 停止监听的方法是removeUpdates(PendingIntent), 栗子:

// Remove the listener you previously added
locationManager.removeUpdates(locationListener);

维护当前最佳的估计数:

我们可能会觉得最近获取的位置信息是最准确的. 但是因为定位精确度的变化, 最近的信息不见得总是最好的. 我们应该根据多个标准来选择最佳位置. 这些标准应该经过用例和现场测试. 这里是几个可以验证精确度的步骤:

1.      检查检索到的位置是否比之前的估计有明显的更新.

2.      检查精度是比以前的更好还是更差.

3.      检查新的位置从哪个provider获取并决定它是否更加值得信任.

实现这一逻辑的栗子大概长这样:

private static final int TWO_MINUTES = 1000 * 60 * 2;

/** Determineswhether one Location reading is better than the current Location fix
  * @param location  The new Location that you want to evaluate
  * @param currentBestLocation  The current Location fix, to whichyou want to compare the new one
  */

protected boolean isBetterLocation(Location location, Location currentBestLocation) {
    if (currentBestLocation == null) {
        // A new location is always better than no location
        return true;
    }

    // Check whetherthe new location fix is newer or older
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;

    // If it's beenmore than two minutes since the current location, use the new location
    // because theuser has likely moved
    if (isSignificantlyNewer) {
        return true;
    // If the newlocation is more than two minutes older, it must be worse
    } else if (isSignificantlyOlder) {
        return false;
    }

    // Check whetherthe new location fix is more or less accurate
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;

    // Check if theold and new location are from the same provider
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());

    // Determinelocation quality using a combination of timeliness and accuracy
    if (isMoreAccurate) {
        return true;
    } else if (isNewer && !isLessAccurate) {
        return true;
    } else if (isNewer && !isSignificantlyLessAccurate &&isFromSameProvider) {
        return true;
    }
    return false;
}

/** Checkswhether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) {
      return provider2 == null;
    }
    return provider1.equals(provider2);
}

调整模型来节省电量和数据交换:

当我们测试APP的时候, 可能会发现我们的模型如果想要既提供好的定位又提供高性能, 可能需要一些调整. 这里是一些我们权衡两者可能要修改的东西:

l  减小窗口的大小: 一个更小的窗口意味着与GPS和网络定位服务更少的交互,所以会节省电池寿命. 但是它也会导致有更少的位置信息可以选择.

l  设置locationproviders来用更低的频率更新: 减少刷新位置信息的频率也可以节省电池的寿命, 但是会牺牲一定的精确度. 如何权衡取决于我们的APP逻辑. 我们可以通过减少requestLocationUpdates()的参数值来降低刷新的频率.

l  约束一组providers:根据APP的应用场景或者需要的精确度等级, 我们可以选择只使用Network Location Provider和GPS中的一个, 而不是全用. 这样可以降低电池消耗, 但是同样会降低精度.

常见应用案例:

有很多我们想要获取用户的位置信息的理由. 下面是两个我们可以用它们来丰富自己的APP的使用场景. 每个场景也描述了何时启动和停止监听器, 以及如何节省电池寿命.

通过位置标记用户创建的内容:

我们可能会需要创建一个APP, 允许用户通过位置信息标记一些内容. 比如分享本地体验, 共享一家餐厅的美食, 或者记录一些包括当前位置的信息. 一个描述这些交互如何发生的模型, 大概长这样:


为了获取最佳的精度, 我们可能选择在用户开始创建它们的内容甚至当应用启动的时候就开始获取用户的位置信息, 而在内容准备发送或者记录的时候停止更新位置信息. 我们可能需要考虑创建内容的任务时间大概多长, 然后决定什么时候开始启动位置信息更新.

帮助用户决定去哪儿:

我们可能会创建一个尝试提供一系列选项建议用户去哪的APP. 比如帮助用户找附近的餐馆, 商店, 娱乐设施并根据用户位置提供推荐的顺序. 要完成这些我们可能会用到:

l  当取得一个新的最佳位置信息的时候, 重新排列推荐目标.

l  如果推荐目标稳定了, 就停止监听位置更新.

该模型如下图所示:


提供模拟位置数据:

如果我们正在开发自己的APP, 那么我们可能会需要测试自己的定位模型工作的如何. 使用真实的Android设备是最简单的方法. 但是如果我们没有一个设备的话, 还是有方法可以测试基于位置功能的APP的, 只需要使用Android模拟器提供模拟位置数据就可以了. 有三种不同的方法发送APP模拟数据: 使用Android Studio, DDMS或者在模拟器终端使用”geo”命令. 详情可以参考这里.

 

总结:

定位是我们在移动设备上常用的功能, Android开发者可以使用android.location或者Google Location Services for Android来开发相关的APP.Android.location中的核心组件是LocationManager, 基本用法就是通过LocationManager查询可用的Location Providers, 包括手机基站, GPS, WiFi, 然后开启或者关闭位置更新监听器来获取位置数据. 在开发的时候, 通常会根据APP逻辑在精度和耗电量之间做权衡.


参考: https://developer.android.com/guide/topics/location/index.html

https://developer.android.com/guide/topics/location/strategies.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章