Android 版 Driver SDK 使用入门

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

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

最低系统要求

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

构建和依赖项配置

Google Maven 制品库中提供驱动程序 SDK 4.99 版及更高版本。

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 Console 项目,或者选择一个现有项目,以便与驱动程序 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. 驱动程序 SDK 依赖于 Navigation SDK,此依赖项的配置方式如下:如果需要特定版本的 Navigation SDK,您需要在 build 配置文件中明确定义它(如下所示),省略上述代码块可让项目始终下载主要发布版本中的 Navigation SDK 的最新版本。 请注意,最新版本的驱动程序 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>

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

如果您在应用中使用 Driver 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 项目,请参阅身份验证和授权

在使用驱动程序 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 Web 令牌 (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 来传播的。