iOS 版 Driver SDK 使用入门

Driver SDK 是一个可集成到您的驱动程序应用中的库。它负责使用驾驶员的位置、路线、剩余距离和预计到达时间来更新 Fleet Engine。它还与 Navigation SDK 集成,为驾驶员提供精细导航说明。

最低系统要求

  • 移动设备必须搭载 iOS 14 或更高版本。
  • Xcode 版本 15 或更高版本。
  • 前提条件

    本指南假定您的应用已实现 Navigation SDK,并且 Fleet Engine 后端已设置且可用。不过,示例代码提供了如何设置 Navigation SDK 的示例。

    您还必须在 Google Cloud 项目中启用 Maps SDK for iOS,并获取 API 密钥

    获取使用权限

    如果您是 Google Workspace 客户,请在初始配置期间创建一个 Workspace 群组(例如 google-maps-platform-sdk-users@workspacedomain.com),并向 Google 提供该名称。这是推荐的方法。 然后,您的工作区群组将被添加到许可名单中,该许可名单会授予对正确的 CocoaPods 代码库的访问权限。确认需要访问权限的用户电子邮件地址和服务帐号电子邮件地址包含在此列表中。

    如果贵组织无法创建工作区群组,请向 Google 发送需要访问这些工件的用户和服务帐号电子邮件地址的列表。

    本地开发

    对于本地开发,只需使用 Cloud SDK 登录即可。

    gcloud

    gcloud auth login
    

    用于登录的电子邮件地址必须是 Workspace 群组的成员。

    自动化(构建系统或持续集成)

    根据最佳实践设置自动化主机:

    • 如果您的进程在 Google Cloud 环境中运行,请使用自动凭据检测。

    • 否则,请将服务帐号密钥文件存储在主机文件系统的安全位置,并适当设置 GOOGLE_APPLICATION_CREDENTIALS 环境变量。

    与凭据关联的服务帐号电子邮件地址必须是 Workspace Goup 的成员。

    项目配置

    您可以使用 CocoaPods 来配置驱动程序 SDK。

    使用 CocoaPods

    要使用 CocoaPods 配置驱动程序 SDK,您需要具备以下条件:

    • CocoaPods 工具:如需安装此工具,请打开终端并运行以下命令。 shell sudo gem install cocoapods 如需了解更多详情,请参阅 CocoaPods 入门指南
    1. 为驱动程序 SDK 创建 Podfile,并使用它来安装 API 及其依赖项:在项目目录中创建名为 Podfile 的文件。此文件定义项目的依赖项。修改 Podfile 并添加依赖项。以下是包含依赖项的示例:

      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      
    2. 保存 Podfile。打开终端并转到包含 Podfile 的目录:

      cd <path-to-project>
      
    3. 运行 pod install 命令。这将安装 Podfile 中指定的 API 及其可能具有的任何依赖项。

      pod install
      
    4. 关闭 Xcode,然后打开(双击)您项目的 .xcworkspace 文件以启动 Xcode。从此刻开始,您必须使用 .xcworkspace 文件打开项目。

    Alpha/Beta 版 SDK

    如需配置 iOS 版驱动程序 SDK 的 Alpha 版或 Beta 版,您需要具备以下条件:

    • CocoaPods 工具:如需安装此工具,请打开终端并运行以下命令。

      sudo gem install cocoapods
      

      如需了解详情,请参阅 CocoaPods 入门指南

    • 您的开发账号已列入 Google 访问权限列表。Alpha 版和 Beta 版 SDK 的 Pod 代码库不是公共源代码。如需访问这些版本,请与 Google 客户工程师联系。工程师将您的开发帐号添加到访问权限列表,然后设置用于身份验证的 Cookie

    将项目列入访问权限列表后,您就可以访问该 Pod。

    1. 为 iOS 版驱动程序 SDK 创建 Podfile,并使用它来安装 API 及其依赖项:在项目目录中创建名为 Podfile 的文件。此文件定义项目的依赖项。修改 Podfile 并添加依赖项。以下是包含依赖项的示例:

      source "https://cpdc-eap.googlesource.com/ridesharing-driver-sdk.git"
      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      
    2. 保存 Podfile。打开终端并转到包含 Podfile 的目录:

      cd <path-to-project>
      
    3. 运行 pod install 命令。这将安装 Podfile 中指定的 API 及其可能具有的任何依赖项。

      pod install
      
    4. 关闭 Xcode,然后打开(双击)您项目的 .xcworkspace 文件以启动 Xcode。从此刻开始,您必须使用 .xcworkspace 文件打开项目。

    安装 XCFramework

    XCFramework 是一个二进制软件包,用于安装驱动程序 SDK。您可以在多个平台(包括使用 M1 芯片组的机器)上使用此软件包。本指南介绍如何将包含驱动程序 SDK 的 XCFramework 手动添加到您的项目中,并在 Xcode 中配置您的构建设置。

    下载 SDK 二进制文件和资源:

    1. 解压缩压缩文件以访问 XCFramework 和资源。

    2. 启动 Xcode,然后打开现有项目或创建新项目。如果您刚开始接触 iOS,请创建一个新项目,并选择 iOS 应用模板。

    3. 在项目组下创建一个框架组(如果还没有)。

    4. 将下载的 gRPCCertificates.bundle 文件拖动到 Xcode 项目的顶层目录中。出现提示时,选择 根据需要复制项目 (Copy items if needed)。

    5. 如需安装驱动程序 SDK,请将 GoogleRidesharingDriver.xcframework 文件拖动到项目中的框架、库和嵌入式内容下。出现提示时,选择 根据需要复制项目 (Copy items if needed)。

    6. 将下载的 GoogleRidesharingDriver.bundle 拖动到 Xcode 项目的顶层目录中。出现提示时,选择 Copy items if needed

    7. 从项目导航器中选择您的项目,然后选择应用的目标。

    8. 打开“Build Phases”标签页,并在“Link Binary with Libraries”中添加以下框架和库(如果它们尚不存在):

      • Accelerate.framework
      • AudioToolbox.framework
      • AVFoundation.framework
      • CoreData.framework
      • CoreGraphics.framework
      • CoreLocation.framework
      • CoreTelephony.framework
      • CoreText.framework
      • GLKit.framework
      • ImageIO.framework
      • libc++.tbd
      • libxml2.tbd
      • libz.tbd
      • LocalAuthentication.framework
      • OpenGLES.framework
      • QuartzCore.framework
      • SystemConfiguration.framework
      • UIKit.framework
      • WebKit.framework
    9. 选择您的项目(而不是具体的目标),并打开 Build Settings(构建设置)标签页。在 Other Linker Flags(其他链接器标记)部分中,为调试和发布添加 ‑ObjC。如果看不到这些设置,请将“构建设置”栏中的过滤条件从基本更改为全部

    实现授权和身份验证

    当您的驱动程序应用生成更新并将其发送到 Fleet Engine 后端时,请求必须包含有效的访问令牌。为了对这些请求进行授权和身份验证,驱动程序 SDK 会根据 GMTDAuthorization 协议调用您的对象。该对象负责提供所需的访问令牌。

    作为应用开发者,您可以选择令牌的生成方式。您的实现应能够执行以下操作:

    • 从 HTTPS 服务器提取访问令牌(可能是 JSON 格式)。
    • 解析并缓存令牌。
    • 在令牌过期时刷新令牌。

    如需详细了解 Fleet Engine 服务器预期的令牌,请参阅创建 JSON Web 令牌 (JWT) 进行授权

    提供方 ID 与 Google Cloud 项目 ID 相同。如需了解详情,请参阅 Fleet Engine Deliveries API 用户指南

    以下示例实现了访问令牌提供程序:

    #import "SampleAccessTokenProvider.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    // SampleAccessTokenProvider.h
    @interface SampleAccessTokenProvider : NSObject<GMTDAuthorization>
    @end
    
    static NSString *const PROVIDER_URL = @"INSERT_YOUR_TOKEN_PROVIDER_URL";
    
    // SampleAccessTokenProvider.m
    @implementation SampleAccessTokenProvider{
      // The cached vehicle token.
      NSString *_cachedVehicleToken;
      // Keep track of the vehicle ID the cached token is for.
      NSString *_lastKnownVehicleID;
      // Keep track of when tokens expire for caching.
      NSTimeInterval _tokenExpiration;
    }
    
    - (void)fetchTokenWithContext:(nullable GMTDAuthorizationContext *)authorizationContext
                       completion:(nonnull GMTDAuthTokenFetchCompletionHandler)completion {
      if (!completion) {
        NSAssert(NO, @"%s encountered an unexpected nil completion.", __PRETTY_FUNCTION__);
        return;
      }
    
      // Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
      NSString *vehicleID = authorizationContext.vehicleID;
      if (!vehicleID) {
        NSAssert(NO, @"Vehicle ID is missing from authorizationContext.");
        return;
      }
    
    // Clear cached vehicle token if vehicle ID has changed.
      if (![_lastKnownVehicleID isEqual:vehicleID]) {
        _tokenExpiration = 0.0;
        _cachedVehicleToken = nil;
      }
      _lastKnownVehicleID = vehicleID;
    
      // Clear cached vehicle token if it has expired.
      if ([[NSDate date] timeIntervalSince1970] > _tokenExpiration) {
        _cachedVehicleToken = nil;
      }
    
      // If appropriate, use the cached token.
      if (_cachedVehicleToken) {
        completion(_cachedVehicleToken, nil);
        return;
      }
      // Otherwise, try to fetch a new token from your server.
      NSURL *requestURL = [NSURL URLWithString:PROVIDER_URL];
      NSMutableURLRequest *request = 
                              [[NSMutableURLRequest alloc] initWithURL:requestURL];
      request.HTTPMethod = @"GET";
      // Replace the following key values with the appropriate keys based on your
      // server's expected response.
      NSString *vehicleTokenKey = @"VEHICLE_TOKEN_KEY";
      NSString *tokenExpirationKey = @"TOKEN_EXPIRATION";
      __weak typeof(self) weakSelf = self;
      void (^handler)(NSData *_Nullable data, NSURLResponse *_Nullable response,
                      NSError *_Nullable error) =
          ^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
            typeof(self) strongSelf = weakSelf;
            if (error) {
              completion(nil, error);
              return;
            }
    
            NSError *JSONError;
            NSMutableDictionary *JSONResponse =
                [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&JSONError];
    
            if (JSONError) {
              completion(nil, JSONError);
              return;
            } else {
              // Sample code only. No validation logic.
              id expirationData = JSONResponse[tokenExpirationKey];
              if ([expirationData isKindOfClass:[NSNumber class]]) {
                NSTimeInterval expirationTime = ((NSNumber *)expirationData).doubleValue;
                strongSelf->_tokenExpiration = [[NSDate date] timeIntervalSince1970] + expirationTime;
              }
              strongSelf->_cachedVehicleToken = JSONResponse[vehicleTokenKey];
              completion(JSONResponse[vehicleTokenKey], nil);
            }
        };
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *mainQueueURLSession =  
           [NSURLSession  sessionWithConfiguration:config delegate:nil
    delegateQueue:[NSOperationQueue mainQueue]];
    NSURLSessionDataTask *task = [mainQueueURLSession dataTaskWithRequest:request completionHandler:handler];
    [task resume];
    }
    
    @end
    

    创建 DeliveryDriverAPI 实例

    如需获取 GMTDDeliveryVehicleReporter 实例,您首先需要使用 providerID、vehicleID、DriverContext 和 accessTokenProvider 创建 GMTDDeliveryDriverAPI 实例。providerID 与 Google Cloud 项目 ID 相同。您可以直接通过驱动程序 API 访问 GMTDDeliveryVehicleReporter 实例。

    以下示例将创建一个 GMTDDeliveryDriverAPI 实例:

    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    - (void)viewDidLoad {
      NSString *vehicleID = @"INSERT_CREATED_VEHICLE_ID";
      SampleAccessTokenProvider *accessTokenProvider = 
                                    [[SampleAccessTokenProvider alloc] init];
      GMTDDriverContext *driverContext = 
         [[GMTDDriverContext alloc] initWithAccessTokenProvider:accessTokenProvider
                                                     providerID:PROVIDER_ID 
                                                  vehicleID:vehicleID 
          navigator:_mapView.navigator];
    
      GMTDDeliveryDriverAPI *deliveryDriverAPI = [[GMTDDeliveryDriverAPI alloc] initWithDriverContext:driverContext];
    }
    

    (可选)监听 VehicleReporter 事件

    locationTrackingEnabled 为 YES 时,GMTDDeliveryVehicleReporter 会定期更新车辆。为了响应这些定期更新,任何对象都可以通过遵循 GMTDVehicleReporterListener 协议来订阅 GMTDDeliveryVehicleReporter 事件。

    您可以处理以下事件:

    • vehicleReporter:didSucceedVehicleUpdate

      通知 Driver 应用后端服务已成功收到车辆位置和状态更新。

    • vehicleReporter:didFailVehicleUpdate:withError

      通知监听器车辆更新失败。只要启用了位置跟踪功能,GMTDDeliveryVehicleReporter 就会继续将最新数据发送到 Fleet Engine 后端。

    以下示例会处理这些事件:

    SampleViewController.h
    @interface SampleViewController : UIViewController<GMTDVehicleReporterListener>
    @end
    
    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      [ridesharingDriverAPI.vehicleReporter addListener:self];
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didSucceedVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate {
      // Handle update succeeded.
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate withError:(NSError *)error {
      // Handle update failed.
    }
    
    @end
    

    启用位置跟踪

    如需启用位置跟踪,您的应用可以在 GMTDDeliveryVehicleReporter 上将 locationTrackingEnabled 设置为 YES。然后,GMTDDeliveryVehicleReporter 将自动发送位置信息更新。当 GMSNavigator 处于导航模式(通过 setDestinations 设置目的地时)且 locationTrackingEnabled 设为 YES 时,GMTDDeliveryVehicleReporter 也会自动发送路线和预计到达时间更新。

    在这些更新期间设置的路线与驾驶员在导航会话期间导航到的路线相同。因此,为确保货运跟踪正常运行,通过 -setDestinations:callback: 设置的航点应与 Fleet Engine 后端中设置的目的地一致。

    以下示例启用位置跟踪:

    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView; 
    }
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      deliveryDriverAPI.vehicleReporter.locationTrackingEnabled = YES;
    }
    
    @end
    

    默认情况下,报告间隔为 10 秒,但可以通过 locationUpdateInterval 更改报告间隔。支持的最短更新间隔为 5 秒。支持的最长更新间隔为 60 秒。更新频率越高,请求速度和错误速度越慢。

    停用位置信息更新

    您的应用可以停用车辆的位置信息更新。例如,当驾驶员的排班结束时,您的应用可以将 locationTrackingEnabled 设置为 NO

      _vehicleReporter.locationTrackingEnabled = NO
    

    处理 update_mask 错误

    GMTDDeliveryVehicleReporter 发送车辆更新时,如果掩码为空,可能会发生 update_mask 错误,并且通常发生在启动后的首次更新中。以下示例展示了如何处理此错误:

    Swift

    import GoogleRidesharingDriver
    
    class VehicleReporterListener: NSObject, GMTDVehicleReporterListener {
      func vehicleReporter(
        _ vehicleReporter: GMTDVehicleReporter,
        didFail vehicleUpdate: GMTDVehicleUpdate,
        withError error: Error
      ) {
        let fullError = error as NSError
        if let innerError = fullError.userInfo[NSUnderlyingErrorKey] as? NSError {
          let innerFullError = innerError as NSError
          if innerFullError.localizedDescription.contains("update_mask cannot be empty") {
            emptyMaskUpdates += 1
            return
          }
        }
        failedUpdates += 1
      }
    
      override init() {
        emptyMaskUpdates = 0
        failedUpdates = 0
      }
    }
    
    

    Objective-C

    #import "VehicleReporterListener.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    @implementation VehicleReporterListener {
      NSInteger emptyMaskUpdates = 0;
      NSInteger failedUpdates = 0;
    }
    
    - (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter
      didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate
                 withError:(NSError *)error {
      for (NSError *underlyingError in error.underlyingErrors) {
        if ([underlyingError.localizedDescription containsString:@"update_mask cannot be empty"]) {
          emptyMaskUpdates += 1;
          return;
        }
      }
      failedUpdates += 1
    }
    
    @end