Android 版 Driver SDK 使用入门

您可以使用 Driver SDK 为“行程和订单进度”应用提供增强的导航和跟踪功能。Driver SDK 可为按需行程和送货解决方案舰队引擎提供车辆位置和任务更新。

Driver SDK 可让 Fleet Engine 服务和自定义服务了解车辆的位置和状态。例如,车辆可以是 ONLINEOFFLINE,并且车辆位置会随着行程的进行而变化。

最低系统要求

移动设备必须搭载 Android 6.0(API 级别 23)或更高版本。

构建和依赖项配置

驱动程序 SDK 4.99 及更高版本可从 Google Maven 制品库获取。

Gradle

请将以下内容添加到 build.gradle 文件:

repositories {
    ...
    google()
}

Maven

请将以下内容添加到 pom.xml 文件:

<project>
  ...
  <repositories>
    <repository>
      <id>google-maven-repository</id>
      <url>https://maven.google.com</url>
    </repository>
  </repositories>
  ...
</project>

项目配置

如需使用驱动程序 SDK,应用必须以 minSdkVersion 23 或更高版本为目标平台。

如需运行使用驱动程序 SDK 构建的应用,Android 设备必须安装 Google Play 服务

设置您的开发项目

如需在 Google Cloud 控制台上设置开发项目并获取该项目的 API 密钥,请执行以下操作:

  1. 创建新的 Google Cloud 控制台项目,或选择现有项目,以便与驱动程序 SDK 搭配使用。等待几分钟,直到新项目显示在 Google Cloud 控制台上。

  2. 为了运行演示版应用,您的项目必须有权访问 Maps SDK for Android。在 Google Cloud 控制台中,依次选择 API 和服务 > 库,然后搜索并启用 Maps SDK for Android。

  3. 依次选择 API 和服务 > 凭据 > 创建凭据 > API 密钥,获取项目的 API 密钥。如需详细了解如何获取 API 密钥,请参阅获取 API 密钥

将驱动程序 SDK 添加到您的应用

驱动程序 SDK 可从 Google Maven 制品库获取。代码库包含 SDK 的项目对象模型 (.pom) 文件和 Javadocs。如需将驱动程序 SDK 添加到您的应用,请执行以下操作:

  1. 将以下依赖项添加到您的 Gradle 或 Maven 配置中,将 VERSION_NUMBER 占位符替换为所需的驱动程序 SDK 版本。

    Gradle

    将以下内容添加到 build.gradle 中:

    dependencies {
      ...
      implementation 'com.google.android.libraries.mapsplatform.transportation:transportation-driver:VERSION_NUMBER'
    }
    

    Maven

    将以下内容添加到 pom.xml 中:

    <dependencies>
      ...
      <dependency>
        <groupId>com.google.android.libraries.mapsplatform.transportation</groupId>
        <artifactId>transportation-driver</artifactId>
        <version>VERSION_NUMBER</version>
      </dependency>
    </dependencies>
    
  2. Driver SDK 依赖于 Navigation SDK,此依赖项的配置方式如下:如果需要特定版本的 Navigation SDK,则需要在 build 配置文件中明确定义,如下所示。如果省略上述代码块,项目将始终在主要发布版本中下载最新版本的 Navigation SDK。请注意,最新版 Driver SDK 和 Navigation SDK 的组合行为在发布之前经过了严格的测试。

    相应地安排开发和发布环境的依赖项配置。

    Gradle

    将以下内容添加到 build.gradle 中:

    dependencies {
      ...
      implementation 'com.google.android.libraries.navigation:navigation:5.0.0'
    }
    

    Maven

    将以下内容添加到 pom.xml 中:

    <dependencies>
      ...
      <dependency>
        <groupId>com.google.android.libraries.navigation</groupId>
        <artifactId>navigation</artifactId>
        <version>5.0.0</version>
      </dependency>
    </dependencies>
    

向应用添加 API 密钥

将驱动程序 SDK 添加到您的应用后,请将 API 密钥添加到您的应用。您必须使用在设置开发项目时获得的项目 API 密钥。

本部分介绍如何存储 API 密钥,以便您的应用可以更安全地引用该密钥。您不应将 API 密钥签入版本控制系统。它应该存储在项目根目录下的 local.properties 文件中。如需详细了解 local.properties 文件,请参阅 Gradle 属性文件

为了简化此任务,您可以使用 Android 版 Secrets Gradle 插件

如需安装此插件并存储您的 API 密钥,请执行以下操作:

  1. 打开根级 build.gradle 文件,并将以下代码添加到 buildscript 下的 dependencies 元素中。

    Groovy

    buildscript {
        dependencies {
            // ...
            classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.0"
        }
    }
    

    Kotlin

    buildscript {
        dependencies {
            // ...
            classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.0")
        }
    }
    
  2. 打开应用级 build.gradle 文件,并将以下代码添加到 plugins 元素中。

    Groovy

    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
    

    Kotlin

    id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
    
  3. 如果您使用 Android Studio,请将项目与 Gradle 同步

  4. 在项目级目录中打开 local.properties,然后添加以下代码。将 YOUR_API_KEY 替换为您的 API 密钥。

    MAPS_API_KEY=YOUR_API_KEY
    
  5. AndroidManifest.xml 文件中,定位到 com.google.android.geo.API_KEY 并按如下所示更新 android:value 属性:

    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="${MAPS_API_KEY}" />
    

以下示例显示了一个示例应用的完整清单:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.driverapidemo">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/_AppTheme">

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

在您的应用中添加必要的提供方说明

如果您在应用中使用驱动程序 SDK,则必须在应用的法律声明部分包含提供方说明文本和开源许可。最好以独立菜单项的形式提供提供方说明,或将提供方说明作为关于菜单项的一部分提供。

许可信息可以在未归档的 AAR 文件中的“third_party_licenses.txt”文件中找到。

如需了解如何添加开源声明,请参阅 https://developers.google.com/android/guides/opensource

依赖项

如果您使用 ProGuard 优化 build,则可能需要将以下行添加到 ProGuard 配置文件中:

-dontwarn com.google.**
-dontwarn okio.**

支持的最低 API 级别为 23。

初始化 SDK

必须提供提供方 ID(通常是 Google Cloud 项目 ID)才能初始化 DriverContext 对象。如需详细了解如何设置 Google Cloud 项目,请参阅身份验证和授权

在使用 Driver SDK 之前,您必须先初始化 Navigation SDK。要初始化 SDK,请执行以下操作:

  1. NavigationApi 获取 Navigator 对象。

    Java

    NavigationApi.getNavigator(
        this, // Activity
        new NavigationApi.NavigatorListener() {
          @Override
          public void onNavigatorReady(Navigator navigator) {
            // Keep a reference to the Navigator (used to configure and start nav)
            this.navigator = navigator;
          }
        }
    );
    

    Kotlin

    NavigationApi.getNavigator(
      this, // Activity
      object : NavigatorListener() {
        override fun onNavigatorReady(navigator: Navigator) {
          // Keep a reference to the Navigator (used to configure and start nav)
          this@myActivity.navigator = navigator
        }
      },
    )
    
  2. 创建一个 DriverContext 对象,填充必填字段。

    Java

    DriverContext driverContext = DriverContext.builder(application)
        .setProviderId(providerId)
        .setVehicleId(vehicleId)
        .setAuthTokenFactory(authTokenFactory)
        .setNavigator(navigator)
        .setRoadSnappedLocationProvider(
            NavigationApi.getRoadSnappedLocationProvider(application))
        .build();
    

    Kotlin

    val driverContext =
      DriverContext.builder(application)
        .setProviderId(providerId)
        .setVehicleId(vehicleId)
        .setAuthTokenFactory(authTokenFactory)
        .setNavigator(navigator)
        .setRoadSnappedLocationProvider(NavigationApi.getRoadSnappedLocationProvider(application))
        .build()
    
  3. 使用 DriverContext 对象初始化 *DriverApi

    Java

    RidesharingDriverApi ridesharingDriverApi = RidesharingDriverApi.createInstance(driverContext);
    

    Kotlin

    val ridesharingDriverApi = RidesharingDriverApi.createInstance(driverContext)
    
  4. 从 API 对象获取 RidesharingVehicleReporter。 (*VehicleReporter 扩展 NavigationVehicleReporter。)

    Java

    RidesharingVehicleReporter vehicleReporter = ridesharingDriverApi.getRidesharingVehicleReporter();
    

    Kotlin

    val vehicleReporter = ridesharingDriverApi.getRidesharingVehicleReporter()
    

正在通过 AuthTokenFactory 进行身份验证

当驱动程序 SDK 生成位置信息更新时,必须将这些更新发送到 Fleet Engine 服务器。为了对这些请求进行身份验证,驱动程序 SDK 将调用调用方提供的 AuthTokenFactory 实例。工厂实例负责在营业地点更新时生成身份验证令牌。

令牌生成方式因每个开发者的情况而异。不过,实现可能需要:

  • 从 HTTPS 服务器提取身份验证令牌(可能采用 JSON 格式)
  • 解析并缓存令牌
  • 请在令牌过期后刷新令牌

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

下面是 AuthTokenFactory 的框架实现:

Java

class JsonAuthTokenFactory implements AuthTokenFactory {
  private String token;  // initially null
  private long expiryTimeMs = 0;

  // This method is called on a thread whose only responsibility is to send
  // location updates. Blocking is OK, but just know that no location updates
  // can occur until this method returns.
  @Override
  public String getToken(AuthTokenContext authTokenContext) {
    if (System.currentTimeMillis() > expiryTimeMs) {
      // The token has expired, go get a new one.
      fetchNewToken(authTokenContext.getVehicleId());
    }
    return token;
  }

  private void fetchNewToken(String vehicleId) {
    String url =
        new Uri.Builder()
            .scheme("https")
            .authority("yourauthserver.example")
            .appendPath("token")
            .appendQueryParameter("vehicleId", vehicleId)
            .build()
            .toString();

    try (Reader r = new InputStreamReader(new URL(url).openStream())) {
      com.google.gson.JsonObject obj
          = com.google.gson.JsonParser.parseReader(r).getAsJsonObject();
      token = obj.get("Token").getAsString();
      expiryTimeMs = obj.get("TokenExpiryMs").getAsLong();

      // The expiry time could be an hour from now, but just to try and avoid
      // passing expired tokens, we subtract 10 minutes from that time.
      expiryTimeMs -= 10 * 60 * 1000;
    } catch (IOException e) {
      // It's OK to throw exceptions here. The StatusListener you passed to
      // create the DriverContext class will be notified and passed along the failed
      // update warning.
      throw new RuntimeException("Could not get auth token", e);
    }
  }
}

Kotlin

class JsonAuthTokenFactory : AuthTokenFactory() {

  private var token: String = ""
  private var expiryTimeMs: Long = 0

  // This method is called on a thread whose only responsibility is to send
  // location updates. Blocking is OK, but just know that no location updates
  // can occur until this method returns.
  override fun getToken(context: AuthTokenContext): String {
    if (System.currentTimeMillis() > expiryTimeMs) {
      // The token has expired, go get a new one.
      fetchNewToken(authTokenContext.getVehicleId())
    }
     return token
  }

  fun fetchNewToken(vehicleId: String) {
    val url =
      Uri.Builder()
        .scheme("https")
        .authority("yourauthserver.example")
        .appendPath("token")
        .appendQueryParameter("vehicleId", vehicleId)
        .build()
        .toString()

    try {
      val reader = InputStreamReader(URL(url).openStream())

      reader.use {
        val obj = com.google.gson.JsonParser.parseReader(r).getAsJsonObject()

        token = obj.get("ServiceToken").getAsString()
        expiryTimeMs = obj.get("TokenExpiryMs").getAsLong()

        // The expiry time could be an hour from now, but just to try and avoid
        // passing expired tokens, we subtract 10 minutes from that time.
        expiryTimeMs -= 10 * 60 * 1000
      }
    } catch (e: IOException) {
      // It's OK to throw exceptions here. The StatusListener you passed to
      // create the DriverContext class will be notified and passed along the failed
      // update warning.
      throw RuntimeException("Could not get auth token", e)
    }
  }
}

此特定实现使用内置的 Java HTTP 客户端从开发者的身份验证服务器提取 JSON 格式的令牌。系统会保存该令牌以供重复使用。如果旧令牌在其到期后的 10 分钟内,系统会重新提取该令牌。

您的实现可能会采取不同的方式,例如使用后台线程刷新令牌。

AuthTokenFactory 中的异常将被视为暂时异常,除非这些异常重复发生。尝试多次后,驱动程序 SDK 会假定错误是永久性的,并停止尝试发送更新。

通过 StatusListener 查看状态和 Error Reporting

由于驱动程序 SDK 在后台执行操作,因此请使用 StatusListener 在发生特定事件(例如错误、警告或调试消息)时触发通知。错误可能是暂时性的(例如 BACKEND_CONNECTIVITY_ERROR),也可能导致位置信息更新永久停止(例如 VEHICLE_NOT_FOUND,表示配置错误)。

您可以提供可选的 StatusListener 实现,如下所示:

Java

class MyStatusListener implements StatusListener {
  /** Called when background status is updated, during actions such as location reporting. */
  @Override
  public void updateStatus(
      StatusLevel statusLevel, StatusCode statusCode, String statusMsg) {
    // Status handling stuff goes here.
    // StatusLevel may be DEBUG, INFO, WARNING, or ERROR.
    // StatusCode may be DEFAULT, UNKNOWN_ERROR, VEHICLE_NOT_FOUND,
    // BACKEND_CONNECTIVITY_ERROR, or PERMISSION_DENIED.
  }
}

Kotlin

class MyStatusListener : StatusListener() {
  /** Called when background status is updated, during actions such as location reporting. */
  override fun updateStatus(statusLevel: StatusLevel, statusCode: StatusCode, statusMsg: String) {
    // Status handling stuff goes here.
    // StatusLevel may be DEBUG, INFO, WARNING, or ERROR.
    // StatusCode may be DEFAULT, UNKNOWN_ERROR, VEHICLE_NOT_FOUND,
    // BACKEND_CONNECTIVITY_ERROR, or PERMISSION_DENIED.
  }
}

有关 SSL/TLS 的注意事项

在内部,驱动程序 SDK 实现使用 SSL/TLS 与 Fleet Engine 服务器进行安全通信。较低版本的 Android(API 版本 19 或更低版本)可能需要 SecurityProvider 补丁才能与服务器通信。如需详细了解如何在 Android 中使用 SSL,请查看这篇文章。本文还包含用于修补安全提供程序的代码示例。

启用位置信息更新

有了 *VehicleReporter 实例后,启用位置信息更新就非常简单:

Java

RidesharingVehicleReporter reporter = ...;

reporter.enableLocationTracking();

Kotlin

val reporter = ...

reporter.enableLocationTracking()

当车辆状态为 ONLINE 时,系统会定期发送位置信息更新。请注意,调用 reporter.enableLocationTracking() 不会自动将车辆状态设置为 ONLINE。您必须明确设置车辆状态

默认情况下,报告间隔为 10 秒。可通过 reporter.setLocationReportingInterval(long, TimeUnit) 更改报告间隔。支持的更新间隔至少为 5 秒。更频繁的更新可能会导致请求和错误变慢。

停用位置信息更新

驾驶员结束轮班后,可以通过调用 DeliveryVehicleReporter.disableLocationTrackingRidesharingVehicleReporter.disableLocationTracking 停止位置信息更新并将车辆标记为离线。

此调用将安排为立即交付一次最终更新,指示车辆处于离线状态。此更新不会包含用户的位置。

设置车辆状态

启用位置信息更新后,将车辆状态设置为 ONLINE 即可让车辆可用于 SearchVehicles 查询;同样,将车辆标记为 OFFLINE 也会将车辆标记为不可用。

您可以选择在服务器端设置车辆状态(请参阅更新车辆),也可以直接在驱动程序 SDK 中设置:

Java

RidesharingVehicleReporter reporter = ...;

reporter.enableLocationTracking();
reporter.setVehicleState(VehicleState.ONLINE);

Kotlin

val reporter = ...

reporter.enableLocationTracking()
reporter.setVehicleState(VehicleState.ONLINE)

启用位置信息更新后,对 setVehicleState 的调用将在下一次位置信息更新时传播。

在未启用位置信息跟踪的情况下,将车辆标记为 ONLINE 将导致 IllegalStateException。如果位置信息跟踪功能尚未启用或明确停用,则可以将车辆标记为 OFFLINE。这将立即进行更新。调用 RidesharingVehicleReporter.disableLocationTracking() 会将车辆状态设为 OFFLINE

请注意,setVehicleState 会立即返回,并且更新是在位置信息更新线程中完成的。与对位置信息更新的错误处理类似,更新车辆状态的错误会使用 DriverContext 中视情况提供的 StatusListener 集来传播。