美文网首页
iOS定位服务设计实例一则

iOS定位服务设计实例一则

作者: fever105 | 来源:发表于2017-07-14 14:18 被阅读533次

    iOS定位服务设计实例一则

    当前,越来越多的移动应用基于LBS(位置服务)构建业务,LBS可以说是移动应用浪潮的基石。每次说到LBS,我们的第一反应就是以百度地图SDK为代表的第三方框架(类似的还有高德,腾讯出品的地图SDK),刻意忽略原生框架Core Location和Map Kit。但问题是:前者真的要比后者好吗?我们对二者到底了解多少?


    A. 定位服务和地图服务

    位置服务由两部分组成:

    1. 定位:即设备位置信息。
    2. 地图:即显示地图和标注地图。

    本文重点介绍定位(服务)。


    A.1 定位服务提供的信息

    通常,定位服务需要提供的信息可划分为两类:

    1. 设备当前坐标,海拔,朝向等基本位置信息

      这些信息由设备上的相关硬件直接提供,不依赖服务器。

    2. 需要检索服务器的地理信息,如坐标反地理编码(城市,地区,街道,门牌号,名称等等),以及基于关键字的poi查询(兴趣点)等。

      这些信息通过向服务器检索获取,可以视为是基于第一类信息的延伸。

    B. 技术方案

    经考量,使用如下策略获取这两类信息:

    • 基本位置信息:通过原生框架Core Location获取。原因如下:

      • 配置项多,有助于精细化控制服务;
      • 信息全面,来源统一;
      • 提供多个节能选项;
      • 相较于第三方框架(如百度地图SDK),无须认证(否则如果百度服务挂了,搞的最基本的定位服务也不能用);
    • 地理信息检索:通过百度地图SDK获取。原因如下:

      • 检索种类多,信息全面(特别是poi信息);
      • 模块清晰,使用简单;
      • 可以配合百度地图服务一起使用;

    下面,我们开始编写自己的定位服务😊

    C. 定位服务

    假设我们要编写一个名为LocationService的定位服务,负责提供定位信息。根据需求,我们为其定义如下Interface:

    /// 单例,全局唯一入口
    + (instancetype)defaultService;
    
    @property (nonatomic, strong, readonly) DDPCLocation *ddpcLocation;
    @property (nonatomic, assign, readonly) CLLocationCoordinate2D coordinate;
    

    具体功能&特性如下:


    C.1 申请位置信息访问权限

    众所周知,位置信息访问权限有两种:

    • When In Use(app在前台时)
    • Always(app运行时,不管在前台还是后台)

    大家也许会注意到,有些app被切换至后台,状态栏处会出现蓝条,显示一条信息:xxxx正在使用你的位置。
    所以,上述权限除了字面所示的区别之外,还有一点需要注意:app被切换至后台,如果开启了后台位置更新,则:

    • When In Use:显示蓝条
    • Always:不显示蓝条

    合理的解释是,对于Always来说,系统认为用户已经充分知晓app会在后台继续访问位置信息,所以不必提示;而对于When In Use来说,系统认为有必要提醒用户app正在后台继续访问位置信息,超出了授权的范围。

    请求授权的代码如下:

    /*** LocationService init ***/
    // 请求授权,always
    [self.locationManager requestAlwaysAuthorization];
    

    C.2 使用CLLocationManager获取基本位置信息

    使用CLLocationManager进行定位的优势如下:

    1. 系统级别的定位信息缓存,,即使CLLocationManager对象刚创建,也可以从中读取到最近一次定位信息。这是因为iOS在系统层面管理定位行为。
    2. 无须任何认证,即可使用,从而保证了服务的稳定性。
    3. 有多个节能设置。由于定位非常耗电,为了增加设备续航,节能就变得异常重要。例如:
    
    /// 使用定位信息的活动类型
    @property(assign, nonatomic) CLActivityType activityType;
    /// 当设备位置可能不再变化时,系统是否可以自动暂停位置更新
    @property(assign, nonatomic) BOOL pausesLocationUpdatesAutomatically;
    /// 移动多少距离,才触发位置更新
    @property(assign, nonatomic) CLLocationDistance distanceFilter;
    
    

    C.3 当前坐标一直有效

    当前,用户未授权访问位置信息时除外。

    正如C.1.2所述,iOS在系统层面缓存了最新定位信息。事实上,CLLocationManager对象一旦创建,就可以从其属性location处获取最近一次定位信息,代码如下:

    /*** LocationService init ***/
    // 读取locationManager中的位置缓存
    self.rawLocation = self.locationManager.location;
    

    随后,一旦有位置更新,我们再将其缓存起来使用:

    /*** CLLocationManagerDelegate ***/ 
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
        // 每次位置更新,记录新的坐标
        self.rawLocation = locations.lastObject;
    }
    

    提醒一点,上述方法中,最新位置的时间戳总是和manager.location的时间相同。也就是说,CLLocationManager首先保存最新位置,再调用进行回调。


    C.4 减少不必要的位置更新,尽可能节能

    正常情况下,一旦开始监听,就会源源不断的收到位置更新,即使设备在原地保持不动。还要注意,更新的频率很高,粗略估算,平均每10秒左右就会有一次更新。很明显,这种信息重复的高频率更新并不是必须的,很多时候"有移动,才更新"的模式更适合业务需求。

    例如,后台要求设备每移动10m,就上报一次位置,那么可以按照如下配置CLLocationManager:

    /*** _locationManager = [[CLLocationManager alloc] init]; ***/
    _locationManager.distanceFilter = 10.0;
    _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    

    这样,既保证了定位的精度,也减少了更新频率,节省设备电力。


    C.5 处理位置授权状态变更

    下述回调不仅在授权状态变更时触发,也会在CLLocationMananger创建后触发:

    - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
    

    所以,这个方法可以是许多关键逻辑的入口。例如,下面的代码首先判断授权状态:如果得到授权,则开始监听位置更新;读取定位缓存;进行反地理编码查询。如果未得到授权,则可以尝试提醒用户。

    /*** locationManager:didChangeAuthorizationStatus: ***/ 
    
        if (self.isAuthorized) { // 已授权
            // 刷新位置
            [self.locationManager startUpdatingLocation];
            
            // 读取locationManager中的位置缓存
            self.rawLocation = self.locationManager.location;
            // 反地理编码
            [self retrieveCurrentLocation];
            // 反地理编码timer
            [self setupTimer];
        } else {
            // TODO: 是否要提醒用户打开位置服务
        }
    

    C.6 系统坐标转换为百度坐标

    一般来说,整个业务体系会使用同一套坐标系,这里假设是百度坐标。原生定位框架给出的坐标是地球坐标,需要客户端进行转换。

    ```s
    不同坐标系:地球坐标,火星坐标和百度坐标
    - 地球坐标(WGS84):国际标准,通过Core Location获取的坐标使用这个坐标系;
    - 火星坐标(GCJ-02):中国标准,高德地图使用这个坐标系;
    - 百度坐标(BD-09):百度地图使用的坐标系;
    
    ```
    

    百度地图SDK在计算工具模块给出了现成的转换方式:

    // 原始坐标
    CLLocationCoordinate2D coor = CLLocationCoordinate2DMake(39.90868, 116.3956);
     
    // 转换WGS84坐标至百度坐标(加密后的坐标)
    NSDictionary *testdic = BMKConvertBaiduCoorFrom(coor,BMK_COORDTYPE_GPS);
     
    // 解密加密后的坐标字典
    // 转换后的百度坐标
    CLLocationCoordinate2D baiduCoor = BMKCoorDictionaryDecode(testdic);
    

    C.7 提供并更新反地理编码信息

    反地理编码的具体实现依赖百度地图SDK。

    至于更新策略,由于每次查询都是一次网络请求,所以有两种:

    1. 使用时再检索,异步实现。适合用量较少的场景;
    2. 定期检索,保存结果,同步实现。适合用量较大的场景;

    根据实际情况,我们选用第二种。

    1. cllocationmanager的distanfiler的问题:
    distanceFileter能够只在移动特定距离时,才调用更新方法,配合locationManager的location属性,后者是最新的,但只是距离未达标,才没有调用更新方法,
    

    D. 地理信息检索

    负责检索工具类名为GeoSearchOperation,其是对百度地图SDK检索操作的封装。根据需求,我们为其定义如下Interface:

    /// 反地理编码查询
    - (void)reverseGeoCodeSearchWithCoordinate:(CLLocationCoordinate2D)coor completionHandler:(void (^)(BOOL isSuccessful, NSArray *results))handler;
    
    /// 检索室内poi,如果city传nil,则表示使用当前定位所在城市,keyword必传
    - (void)poiSearchWithCity:(NSString *)city keyword:(NSString *)keyword completionHandler:(void (^)(BOOL isSuccessful, NSArray *results))handler;
    

    具体功能&特性如下:


    D.1 使用百度地图SDK检索模块实现

    在信息检索方面,由于Core Location框架提供的信息有限,本土化的第三方位置服务框架表现更优秀。


    D.2 区分检索类型

    两种检索类型:

    1. 反地理编码:根据坐标查询街道,城市等信息;
    2. poi:根据关键字,查询"兴趣点"(point of interest);

    D.3 问题

    百度地图SDK的接入,会带来两个问题:

    1. 百度地图SDK要求在app启动时进行注册,否则无法调用服务;
    2. 百度地图服务必须配合SDK自带的定位服务使用;

    因此,还需处理以下逻辑:

    1. 注册百度地图SDK;
    2. 封装SDK定位服务,供百度地图服务使用;

    E. 注册百度地图SDK

    BMKAuthentication负责注册百度地图SDK,并处理可能发生的错误。根据需求,我们为其定义如下Interface:

    /// 单例,全局唯一入口
    + (instancetype)defaultAuthentication;
    
    /// 注册百度地图
    - (void)authenticate;
    

    具体功能&特性如下:


    E.1 尽早注册

    在app生命周期的初始阶段,尽可能早的完成注册。由于业务围绕LBS展开,所以app在很多方面强依赖于百度SDK;无法调用SDK服务,意味着业务瘫痪。

    一般来说,在app启动之初注册即可,但最好先于其他逻辑:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [[DDBMKAuthentication defaultAuthentication] authenticate];
        // 其他启动逻辑
    }
    
    

    E.2 失败重试

    只在由于网络原因失败时重试,其他情况一律不重试,因为没有意义。例如,因为ak失效或配额超限而注册失败,不管怎么重试,都毫无意义。

    BMKMapManager通过下述回调方法告诉我们网络是否存在问题:

    - (void)onGetNetworkState:(int)iError {
        if (iError == 0) {
            self.hasNetworkFailure = NO;
        } else {
            NSLog(@"网络错误,百度地图注册失败");
            self.hasNetworkFailure = YES;
        }
    }
    

    F. 封装SDK定位服务

    DDBMKLocationService是对百度地图SDK定位服务的封装。根据需求,我们为其定义如下Interface:

    /// 进行定位,定位成功,代理方法被调用。注意,只定位一次。
    - (void)locate;
    
    /// delegate
    @property (nonatomic, weak) id<DDBMKLocationServiceDelegate> delegate;
    /** 百度 location */
    @property (nonatomic, strong, readonly) BMKUserLocation *BMKUserLocation;
    

    此外,其还定义了协议DDBMKLocationServiceDelegate,作为定位成功后的回调。

    @protocol DDBMKLocationServiceDelegate <NSObject>
    
    /// 百度sdk定位更新,会调用这个方法
    - (void)BMKLocationService:(DDBMKLocationService *)BMKLocationService didUpdateBMKUserLocation:(BMKUserLocation *)userLocation;
    
    @end
    

    具体功能&特性如下:


    F.1 仅定位一次,不持续定位

    每次调用定位方法locate,仅定位一次,一旦回调,不管成功或失败,都停止,不持续定位。从而避免了在功能上与LocationService重叠,也节省了资源。

    // 定位
    - (void)locate {
        [self.BMKLocationService startUserLocationService];
    }
    
    #pragma mark - BMKLocationServiceDelegate
    
    - (void)didUpdateBMKUserLocation:(BMKUserLocation *)userLocation {
        NSLog(@"百度定位成功✨");
        
        self.BMKUserLocation = userLocation;
        // 停止定位
        [self.BMKLocationService stopUserLocationService];
    }
    
    

    F.2 随地图对象释放,不驻留内存

    由于这个类仅服务百度地图,所以其应该随着地图的创建而创建,释放而释放。

    参考资料:

    1. Location and Maps Programming Guide
    2. 百度地图-iOSSDK-开发指南
    3. 百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系互转

    相关文章

      网友评论

          本文标题:iOS定位服务设计实例一则

          本文链接:https://www.haomeiwen.com/subject/jyonhxtx.html