Envía solicitudes por lotes

En este documento, se muestra cómo agrupar llamadas a la API a fin de reducir la cantidad de conexiones que debe hacer el cliente.

Este documento trata, en particular, sobre cómo realizar una solicitud por lotes mediante la biblioteca cliente de Java. Un ejemplo básico también está disponible en la biblioteca cliente de la API de Google para .NET. El sistema por lotes para la API de EMM de Google Play usa la misma sintaxis HTTP que el sistema de procesamiento por lotes OData.

Descripción general

Cada solicitud que tu cliente realiza a través de la API de EMM de Google Play genera una cierta sobrecarga. La API de EMM de Google Play admite el procesamiento por lotes, a fin de permitir que tu cliente coloque varias llamadas a la API en una sola solicitud.

Estos son algunos ejemplos de situaciones en las que podrías querer utilizar el procesamiento por lotes:

  • Se acaba de inscribir un dominio y ahora tiene muchos datos para subir.
  • Un usuario realizó cambios en los datos mientras la aplicación estaba sin conexión, por lo que esta debe sincronizar una gran cantidad de datos locales con el servidor.

En casos como estos, en lugar de enviar cada llamada por separado, puedes agruparlas en una sola solicitud. Incluso puedes agrupar solicitudes para varios usuarios o para varias API de Google.

Sin embargo, está limitado a 1, 000 llamadas por solicitud de lote. Si necesitas hacer más llamadas, usa varias solicitudes por lotes.

Detalles del lote

Una solicitud por lotes consta de varias llamadas a la API combinadas en una solicitud JSON-RPC. En esta sección, se describe en detalle la sintaxis de la solicitud por lotes, junto con un ejemplo.

Nota: Un conjunto de solicitudes n agrupadas se considera en tu límite de uso como solicitudes n, no como una sola. La solicitud por lotes se divide en un conjunto de solicitudes antes de procesarse.

Formato de una solicitud por lotes

La biblioteca cliente de Java contiene llamadas para crear solicitudes para cada llamada a la API de Google Play EMM. Por ejemplo, para enumerar todas las apps instaladas en un dispositivo, usa lo siguiente:

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

Hay una llamada batch() adicional que puede poner en cola varias solicitudes, como se muestra aquí:

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();
Cuando se llama a batchRequest.execute(), todas las solicitudes en cola se envían a la vez al servidor como un arreglo JSON. El servidor aplica los encabezados y los parámetros de consulta de la solicitud externa (según corresponda) a cada parte y, luego, trata cada parte como si fuera una solicitud JSON separada.

Respuesta a una solicitud por lotes

El servidor ejecuta cada solicitud separada y agrupa el resultado en una sola respuesta hecha de un arreglo único. La biblioteca cliente divide esta respuesta en respuestas individuales y cada una se envía a la función de devolución de llamada que se pasa a queue(). La devolución de llamada es una interfaz que define un método para la falla y un método para el éxito. Por ejemplo, callback1 se implementará como una instancia de lo siguiente:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

Nota: El servidor puede realizar tus llamadas en cualquier orden, por lo que no debes depender de la recepción de resultados en el orden que especifiques en la solicitud. Si quieres asegurarte de que dos llamadas ocurran en un orden determinado, no puedes enviarlas en una sola solicitud. En su lugar, envía la primera solicitud sola y espera una respuesta antes de enviar la segunda.

Ejemplo de solicitud por lotes

El siguiente ejemplo muestra cómo enumerar todas las aplicaciones instaladas en todos los dispositivos de usuario determinados. Las primeras llamadas se usan para obtener el ID de la empresa y del usuario, y se deben ejecutar de forma secuencial. Una vez que se obtengan todos los ID de dispositivo con enterprise.devices().list(), podemos realizar una solicitud por lotes para recuperar todas las aplicaciones de todos los dispositivos del usuario a la 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();
  }
}