发送批量请求

本文档介绍了如何对 API 调用进行批处理以减少客户端必须建立的连接数。

本文档专门介绍了如何使用 Java 客户端库发出批量请求。 适用于 .NET 的 Google API 客户端库。Google Play EMM API 的批处理系统使用相同的 HTTP 语法与 OData 批处理系统相同。

概览

您的客户端通过 Google Play EMM API 发出的每个请求都会产生一定的开销。Google Play EMM API 支持批处理,这样您的客户端就可以将多个 API 调用组合为一个请求。

以下是您可能需要使用批处理的一些示例:

  • 网域刚刚注册,目前有大量数据要上传。
  • 用户在应用离线时对数据进行了更改,因此应用需要与服务器同步大量本地数据。

在这类情况下,您可以将这些调用组合为一个请求,而不是单独发送每个调用。您甚至可以对多个用户或多个 Google API 的请求进行分组。

不过,单个批量请求中最多只能调用 1000 次。如果您需要发出更多调用,请使用多个批量请求。

批量详情

批量请求由组合到一个 JSON-RPC 请求中的多个 API 调用组成。本部分详细介绍了批量请求的语法,并在后面的部分中提供了示例

注意:一组一起进行批处理的 n 个请求将按 n 个请求(而非一个请求)计入用量限额。在处理之前,系统会将批量请求拆分为一组请求。

批量请求的格式

Java 客户端库包含为每个 Google Play EMM API 调用创建请求的调用。例如,如需列出设备上安装的所有应用,您可以使用以下代码:

AndroidEnterprise enterprise = ...;
InstallsListResponse response = enterprise.installs().list(enterpriseId, userId, deviceId)
  .execute();

还有一个额外的 batch() 调用可以将多个请求加入队列,如下所示:

AndroidEnterprise enterprise = ...;
BatchRequest batchRequest = enterprise.batch();
enterprise.installs().list(enterpriseId, userId, deviceId1).queue(batchRequest, callback1);
enterprise.installs().list(enterpriseId, userId, deviceId2).queue(batchRequest, callback2);
enterprise.installs().list(enterpriseId, userId, deviceId3).queue(batchRequest, callback3);
batchRequest.execute();
调用 batchRequest.execute() 时,所有已加入队列的请求都会以 JSON 数组的形式一次性发送到服务器。服务器会将外部请求的查询参数和标头(如果适用)应用于每个部分,然后将每个部分视为单独的 JSON 请求进行处理。

响应批处理请求

服务器执行每个单独的请求,并将结果分组为由单个数组构成的单个响应。客户端库将此响应拆分为单独的响应,每个响应都会发送到传递给 queue() 的回调函数。回调是一个接口,定义了失败方法和成功方法。例如,callback1 会作为以下内容的一个实例来实现:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

  @Override
  public void onSuccess(InstallsListResponse response, HttpHeaders responseHeaders) {
    ...
  }

  @Override
  public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
    ...
  }
}

注意:服务器可能会以任何顺序执行您的调用,因此请勿依赖按照请求中指定的顺序接收结果。如果要确保按指定顺序执行两个调用,不能在单个请求中发送它们;而是单独发送第一个请求,并等待响应后再发送第二个请求。

批量请求示例

以下示例展示了如何列出所有给定用户设备上安装的所有应用。第一批调用用于获取企业 ID 和用户 ID,因此必须按顺序执行。通过 enterprise.devices().list() 获取所有设备 ID 后,我们可以执行批量请求,一次性检索用户所有设备上的所有应用。

package com.google.playenterprise.example;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.androidenterprise.AndroidEnterprise;
import com.google.api.services.androidenterprise.AndroidEnterprise.Installs;
import com.google.api.services.androidenterprise.AndroidEnterpriseScopes;
import com.google.api.services.androidenterprise.model.Device;
import com.google.api.services.androidenterprise.model.DevicesListResponse;
import com.google.api.services.androidenterprise.model.Enterprise;
import com.google.api.services.androidenterprise.model.Install;
import com.google.api.services.androidenterprise.model.InstallsListResponse;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Lists all the apps installed on all devices of a given user.
 */
public class ListAllInstalls {
  private AndroidEnterprise enterprise;
  private final List<String> installList = new ArrayList<>();

  public static void main(String[] argv) throws Exception {
    if (argv.length != 2) {
      throw new IllegalArgumentException("Usage: ListAllInstalls email jsonFilename");
    } else if (!argv[0].contains("@")) {
      throw new IllegalArgumentException("First parameter should be a valid email.");
    }
    new ListAllInstalls().run(argv[0], argv[1]);
  }

  private void run(String userEmail, String jsonKeyPath) throws IOException {
    enterprise = createAndroidEnterprise(jsonKeyPath);

    // Get the enterprise id, user id, and user devices.
    String domain = userEmail.split("@")[1];
    List<Enterprise> results = enterprise.enterprises().list(domain).execute().getEnterprise();
    if (results.isEmpty()) {
      throw new RuntimeException("No enterprise found.");
    }
    String enterpriseId = results.get(0).getId();
    String userId = enterprise
        .users()
        .list(enterpriseId, userEmail)
        .execute()
        .getUser()
        .get(0)
        .getId();
    List<Device> devices = getAllDevices(enterpriseId, userId);

    // Batch all calls to get installs on all user devices.
    gatherAllInstalls(enterpriseId, userId, devices);

    for (String entry : installList) {
      // Do something.
      System.out.println(entry);
    }
  }

  private List<Device> getAllDevices(String enterpriseId, String userId) throws IOException {
    DevicesListResponse devices = enterprise.devices().list(enterpriseId, userId).execute();
    return devices.getDevice();
  }

  private void gatherAllInstalls(String enterpriseId, String userId, List<Device> devices)
      throws IOException {
    BatchRequest batchRequest = enterprise.batch();
    for (Device device : devices) {
      Installs.List list = enterprise
          .installs().list(enterpriseId, userId, device.getAndroidId());
      // Each callback can take the specifics of the associated request in its constructor.
      list.queue(batchRequest, new InstallsCallback(device.getAndroidId()));
    }
    // Executes all the queued requests and their callbacks, single-threaded.
    batchRequest.execute();
  }

  private class InstallsCallback extends JsonBatchCallback<InstallsListResponse> {
    private final String androidId;

    InstallsCallback(String androidId) {
      this.androidId = androidId;
    }

    @Override
    public void onSuccess(InstallsListResponse response, HttpHeaders responseHeaders) {
      for (Install install : response.getInstall()) {
        installList.add(androidId + "," + install.getProductId());
      }
    }

    @Override
    public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
      throw new RuntimeException("Error fetching a device");
    }
  }

  private AndroidEnterprise createAndroidEnterprise(String jsonKeyPath) throws IOException {
    HttpTransport httpTransport = new NetHttpTransport();
    JsonFactory jsonFactory = new JacksonFactory();

    InputStream is = new BufferedInputStream(new FileInputStream(jsonKeyPath));
    final Credential credential = GoogleCredential.fromStream(is, httpTransport, jsonFactory)
        .createScoped(AndroidEnterpriseScopes.all());

    HttpRequestInitializer httpRequestInitializer = new HttpRequestInitializer() {
      @Override
      public void initialize(HttpRequest request) throws IOException {
        credential.initialize(request);
      }
    };
    return new AndroidEnterprise.Builder(httpTransport, jsonFactory, httpRequestInitializer)
        .build();
  }
}