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

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

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

Обзор

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

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

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

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

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

Сведения о партии

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

Примечание . Набор из 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) {
    ...
  }
}

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

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

В следующем примере показано, как составить список всех приложений, установленных на всех устройствах данного пользователя. Первые вызовы используются для получения идентификатора предприятия и пользователя и, соответственно, должны выполняться последовательно. Как только все идентификаторы устройств будут получены с помощью 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();
  }
}