バッチ リクエストの送信

このドキュメントでは、複数の API 呼び出しをバッチ処理して、クライアントが確立する必要がある接続の数を減らす方法について説明します。

このドキュメントでは、特に Java クライアント ライブラリを使用したバッチ リクエストの作成について説明します。 基本的な例については、 .NET 用の Google API クライアント ライブラリ。Google Play EMM API のバッチシステムは同じ HTTP OData バッチ処理システムとして使用されています。

概要

クライアントが Google Play EMM API を介してリクエストを行うたびに、ある程度のオーバーヘッドが発生します。Google Play EMM API ではバッチ処理がサポートされており、クライアントは複数の API 呼び出しを 1 つのリクエストにまとめることができます。

バッチ処理は次のような状況で使用します。

  • ドメインを登録したばかりで、アップロードするデータがたくさんあるので、
  • アプリケーションがオフラインのときにユーザーがデータを変更したため、アプリケーションは大量のローカルデータをサーバーと同期する必要があります。

このような場合は、各通話を個別に送信する代わりに、1 つのリクエストにまとめることができます。複数のユーザーや複数の Google API に対するリクエストをグループ化することもできます。

ただし、1 つのバッチ リクエストに含めることができる呼び出しは 1,000 個までです。それよりも多くの呼び出しを行う必要がある場合は、複数のバッチ リクエストを使用します。

バッチ リクエストの詳細

バッチ リクエストは、複数の API 呼び出しを 1 つの JSON-RPC リクエストに統合したものです。このセクションでは、バッチ リクエストの構文について詳しく説明し、次のセクションでを示します。

: n 件のリクエストを 1 つにまとめたバッチ リクエストは、1 件のリクエストではなく 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 リクエストのように扱います。

バッチ リクエストへのレスポンス

サーバーは各リクエストを個別に実行し、結果を 1 つの配列で構成された 1 つのレスポンスにグループ化します。クライアント ライブラリはこのレスポンスを個別のレスポンスに分割し、それぞれが queue() に渡されたコールバック関数に送信されます。コールバックは、失敗のメソッドと成功のメソッドを定義するインターフェースです。たとえば、callback1 は次のインスタンスとして実装されます。

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

: サーバーは任意の順序で呼び出しを実行する可能性があるため、リクエストで指定された順序で結果を受け取る必要はありません。2 つの呼び出しが特定の順序で行われるようにしたい場合、1 つのリクエストで送信することはできません。代わりに、最初のリクエストを単独で送信し、レスポンスを待ってから 2 番目のリクエストを送信します。

バッチ リクエストの例

次の例は、特定のユーザー デバイスにインストールされているすべてのアプリを一覧表示する方法を示しています。最初の呼び出しは、企業とユーザーの 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();
  }
}