Envoyer des requêtes groupées

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

Ce document explique spécifiquement comment effectuer une requête par lot à l'aide de la bibliothèque cliente Java. Vous trouverez un exemple de base 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 le même protocole que le système de traitement par lot OData.

Présentation

Chaque demande envoyée par votre client via l'API Google Play EMM entraîne des frais généraux. L'API EMM Google Play prend en charge les requêtes par lot, ce qui permet à votre client d'intégrer 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 dispose désormais d'un important volume de données à importer.
  • Un utilisateur a apporté des modifications aux données alors que votre application était hors connexion. Votre application doit donc synchroniser de nombreuses données locales avec le serveur.

Dans ce cas, au lieu d'envoyer chaque appel séparément, vous pouvez les regrouper dans une seule demande. 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, utilisez plusieurs requêtes par lot.

Informations sur les lots

Une requête par lot consiste en plusieurs appels d'API combinés en une seule requête JSON-RPC. Cette section décrit en détail la syntaxe des requêtes par lot, 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. Avant d'être traitée, la requête par lot est décomposée en un ensemble de requêtes.

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 d'API EMM Google Play. Par exemple, pour lister toutes les applications installées sur un appareil, utilisez les éléments suivants:

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

Un appel batch() supplémentaire peut mettre plusieurs requêtes en file d'attente, comme illustré ici:

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 la forme d'un tableau JSON. Le serveur applique les paramètres et les en-têtes de requête de la requête externe (le cas échéant) à 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 et regroupe le résultat en une seule réponse composée d'un seul tableau. La bibliothèque cliente divise cette réponse en réponses individuelles, qui sont envoyées à la fonction de rappel transmise à queue(). Le rappel est une interface définissant une méthode pour l'échec et une méthode pour réussir. Par exemple, callback1 serait implémenté comme l'instance suivante:

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. Ne vous fiez donc pas à la réception des résultats dans l'ordre spécifié dans votre requête. Si vous voulez 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 seconde.

Exemple de requête par lot

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

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