Enviar solicitações em lote

Neste documento, mostramos como agrupar chamadas de API em lote para reduzir o número de conexões que o cliente precisa fazer.

Este documento trata especificamente sobre como fazer uma solicitação em lote usando a biblioteca cliente Java. Um exemplo básico também está disponível na Biblioteca de cliente da API do Google para .NET. O sistema de lote da API Google Play EMM usa o mesmo protocolo como o sistema de processamento em lote OData (em inglês).

Visão geral

Cada solicitação que seu cliente faz pela API Google Play EMM resulta em uma determinada sobrecarga. A API Google Play EMM é compatível com o agrupamento em lote para permitir que seu cliente faça várias chamadas de API em uma única solicitação.

Veja a seguir alguns exemplos de situações em que convém usar lotes:

  • Um domínio acabou de ser registrado e agora tem muitos dados para upload.
  • Um usuário fez alterações nos dados enquanto seu aplicativo estava off-line, por isso seu aplicativo precisa sincronizar uma grande quantidade de dados locais com o servidor.

Nesses casos, em vez de enviar cada chamada separadamente, você pode agrupá-las em uma única solicitação. Você pode até mesmo agrupar solicitações para vários usuários ou para várias APIs do Google.

No entanto, há um limite de 1.000 chamadas em uma única solicitação em lote. Se você precisar fazer mais chamadas do que isso, use várias solicitações em lote.

Detalhes do lote

Uma solicitação em lote consiste em várias chamadas de API combinadas em uma solicitação JSON-RPC. Esta seção descreve a sintaxe de solicitação em lote em detalhes, com um exemplo na seção a seguir.

Observação: um conjunto de n solicitações em lote é contabilizado, no seu limite de uso, como n solicitações, e não como uma única. A solicitação em lote é separada em um conjunto de solicitações antes do processamento.

Formato de uma solicitação em lote

A biblioteca de cliente Java contém chamadas para criar solicitações para cada chamada da API Google Play EMM. Por exemplo, para listar todos os apps instalados em um dispositivo, use o seguinte comando:

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

Há uma chamada batch() extra que pode enfileirar várias solicitações, como mostrado aqui:

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();
Quando batchRequest.execute() é chamado, todas as solicitações na fila são enviadas de uma só vez ao servidor como uma matriz JSON. O servidor aplica os cabeçalhos e parâmetros de consulta da solicitação externa (conforme apropriado) a cada parte e, em seguida, trata cada parte como se fosse uma solicitação JSON separada.

Resposta a uma solicitação em lote

O servidor executa cada solicitação separada e agrupa o resultado em uma única resposta composta de uma única matriz. A biblioteca de cliente divide essa resposta em respostas individuais, e cada uma é enviada para a função de callback transmitida para queue(). O callback é uma interface que define um método para falhas e outro para o sucesso. Por exemplo, callback1 seria implementado como uma instância do seguinte:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

Observação: o servidor pode realizar as chamadas em qualquer ordem. Por isso, não dependa do recebimento dos resultados na ordem especificada na solicitação. Se quiser garantir que duas chamadas ocorram em determinada ordem, não é possível enviá-las em uma única solicitação. Envie a primeira solicitação e aguarde uma resposta antes de enviar a segunda.

Exemplo de solicitação em lote

O exemplo a seguir mostra como listar todos os apps instalados em todos os dispositivos de um determinado usuário. As primeiras chamadas são usadas para obter o ID da empresa e do usuário e, portanto, devem ser executadas em sequência. Depois que todos os IDs de dispositivos forem recebidos com enterprise.devices().list(), poderemos fazer uma solicitação em lote para recuperar todos os aplicativos nos dispositivos do usuário de uma só vez.

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