Envoyer des requêtes groupées

Ce document explique comment autoriser les appels d'API par lot pour réduire le nombre de connexions que votre client doit établir.

Ce document concerne spécifiquement l'envoi d'une requête par lot à l'aide de la bibliothèque cliente Java. Un exemple de base est également disponible dans la bibliothèque cliente des API Google pour .NET. Le système de requêtes par lot de l'API EMM Google Play utilise la même syntaxe HTTP que le système de traitement par lot OData.

Présentation

Chaque demande effectuée par votre client via l'API Google Play EMM entraîne une surcharge. L'API Google Play EMM est compatible avec le traitement par lot, ce qui permet à votre client d'envoyer plusieurs appels d'API dans une seule requête.

Voici quelques exemples de situations dans lesquelles vous pouvez utiliser le traitement par lot:

  • Un domaine vient d'être enregistré et doit maintenant importer de nombreuses données.
  • Un utilisateur a modifié des données alors que votre application était hors connexion. Celle-ci doit donc synchroniser un grand nombre de données locales avec le serveur.

Dans ce cas, au lieu d'envoyer chaque appel séparément, vous pouvez les regrouper en une seule requête. Vous pouvez même regrouper des requêtes pour plusieurs utilisateurs ou pour plusieurs API Google.

Vous êtes toutefois limité à 1 000 appels par requête par lot. Si vous devez effectuer plus d'appels que cela, utilisez plusieurs requêtes par lot.

Informations sur les lots

Une requête par lot est constituée de plusieurs appels d'API combinés en une seule requête JSON-RPC. Cette section décrit la syntaxe des requêtes par lot en détail, avec un exemple dans la section suivante.

Remarque : Un ensemble de n requêtes regroupées est comptabilisé comme n requêtes dans votre limite d'utilisation, et non comme une seule. La requête par lot est décomposée en un ensemble de requêtes avant son traitement.

Format d'une requête par lot

La bibliothèque cliente Java contient des appels permettant de créer des requêtes pour chaque appel de l'API EMM Google Play. Par exemple, pour lister toutes les applications installées sur un appareil, vous devez utiliser:

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

Un autre appel batch() peut mettre plusieurs requêtes en file d'attente, comme illustré ci-dessous:

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();
Lorsque batchRequest.execute() est appelé, toutes les requêtes en file d'attente sont envoyées simultanément au serveur sous forme de tableau JSON. Le serveur applique les paramètres de requête et les en-têtes (selon le cas) de la requête externe à chaque partie, puis traite chaque partie comme s'il s'agissait d'une requête JSON distincte.

Réponse à une requête par lot

Le serveur exécute chaque requête distincte et regroupe le résultat en une seule réponse à partir d'un seul tableau. La bibliothèque cliente divise cette réponse en réponses individuelles, et chacune est envoyée à la fonction de rappel transmise à queue(). Le rappel est une interface qui définit une méthode d'échec et une méthode de réussite. Par exemple, callback1 serait implémenté comme une instance de:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

Remarque : Le serveur peut effectuer vos appels dans n'importe quel ordre. Par conséquent, ne comptez pas sur la réception des résultats dans l'ordre spécifié dans votre requête. Si vous souhaitez vous assurer que deux appels se produisent dans un ordre donné, vous ne pouvez pas les envoyer dans une seule requête. Envoyez plutôt la première requête seule et attendez une réponse avant d'envoyer la deuxième.

Exemple de requête par lot

L'exemple suivant montre comment répertorier toutes les applications installées sur l'ensemble des appareils d'un utilisateur donné. Les premiers appels sont utilisés pour obtenir l'identifiant de l'entreprise et de l'utilisateur, et doivent donc être exécutés de manière séquentielle. Une fois que tous les ID d'appareil ont été obtenus avec enterprise.devices().list(), nous pouvons effectuer une requête par lot pour récupérer toutes les applications sur tous les appareils de l'utilisateur à la fois.

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