バッチ リクエストの送信

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

このドキュメントでは、Java クライアント ライブラリを使用してバッチ リクエストを行う方法について説明します。.NET の Google API クライアント ライブラリで簡単な例を確認することもできます。Google Play EMM API のバッチシステムは、OData バッチ処理システムと同じ HTTP 構文を使用します。

概要

クライアントが 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 つのリクエストで送信することはできません。代わりに、最初のリクエストだけを送信し、そのレスポンスを待ってから次のリクエストを送信します。

バッチ リクエストの例

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