Отправка пакетных запросов

В этом документе показано, как группировать вызовы API, чтобы уменьшить количество подключений, которые должен выполнить ваш клиент.

Этот документ посвящен созданию пакетного запроса с использованием клиентской библиотеки Java. Базовый пример также доступен в клиентской библиотеке Google API для .NET . Пакетная система для Google Play EMM API использует тот же синтаксис HTTP, что и система пакетной обработки OData .

Обзор

Каждый запрос, который ваш клиент делает через API EMM Google Play, приводит к определенным накладным расходам. API EMM Google Play поддерживает пакетную обработку, что позволяет вашему клиенту объединить несколько вызовов API в один запрос.

Вот несколько примеров ситуаций, в которых вы можете использовать пакетную обработку:

  • Домен только что зарегистрирован, и теперь в него нужно загрузить много данных.
  • Пользователь внес изменения в данные, пока ваше приложение находилось в автономном режиме, поэтому вашему приложению необходимо синхронизировать большой объем локальных данных с сервером.

В таких случаях вместо отправки каждого вызова отдельно вы можете сгруппировать их в один запрос. Вы даже можете группировать запросы для нескольких пользователей или для нескольких API Google.

Однако вы ограничены 1000 вызовами в одном пакетном запросе. Если вам нужно сделать больше вызовов, используйте несколько пакетных запросов.

Детали партии

Пакетный запрос состоит из нескольких вызовов API, объединенных в один запрос JSON-RPC. В этом разделе подробно описан синтаксис пакетного запроса, а в следующем разделе приведен пример .

Примечание . Набор из n запросов, объединенных вместе, учитывается при расчете лимита использования как n запросов, а не как один запрос. Перед обработкой пакетный запрос разбивается на набор запросов.

Формат пакетного запроса

Клиентская библиотека Java содержит вызовы для создания запросов для каждого вызова API EMM Google Play. Например, чтобы перечислить все приложения, установленные на устройстве, вы должны использовать следующее:

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) {
    ...
  }
}

Примечание . Сервер может выполнять ваши вызовы в любом порядке, поэтому не рассчитывайте на получение результатов в порядке, указанном в вашем запросе. Если вы хотите гарантировать, что два вызова выполняются в заданном порядке, вы не можете отправить их в одном запросе; вместо этого отправьте первый запрос отдельно и дождитесь ответа, прежде чем отправлять второй.

Пример пакетного запроса

В следующем примере показано, как составить список всех приложений, установленных на всех устройствах пользователя. Первые вызовы используются для получения идентификатора предприятия и пользователя и соответственно должны выполняться последовательно. Как только все идентификаторы устройств будут получены с помощью enterprise.devices().list() , мы можем выполнить пакетный запрос для одновременного получения всех приложений на всех устройствах пользователя.

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();
  }
}