Inviare richieste batch

Questo documento mostra come raggruppare le chiamate API in batch per ridurre il numero di connessioni che il client deve effettuare.

Questo documento riguarda in particolare la creazione di una richiesta batch utilizzando la libreria client Java. Un esempio di base è disponibile anche in Libreria client delle API di Google per .NET. Il sistema batch per l'API EMM di Google Play utilizza lo stesso HTTP della sintassi del sistema di elaborazione batch OData.

Panoramica

Ogni richiesta effettuata dal client tramite l'API EMM di Google Play comporta un certo overhead. L'API EMM di Google Play supporta il raggruppamento in batch, per consentire al tuo client di inserire più chiamate API in una singola richiesta.

Di seguito sono riportati alcuni esempi di situazioni in cui si potrebbe utilizzare la creazione in batch:

  • Un dominio è stato appena registrato e ora ci sono molti dati da caricare.
  • Un utente ha apportato modifiche ai dati mentre la tua applicazione era offline, quindi l'applicazione deve sincronizzare una grande quantità di dati locali con il server.

In questi casi, invece di inviare ogni chiamata separatamente, puoi raggrupparle in un'unica richiesta. Puoi anche raggruppare le richieste per più utenti o per più API di Google.

Tuttavia, esiste un limite di 1000 chiamate in una singola richiesta batch. Se devi effettuare un numero maggiore di chiamate, utilizza più richieste batch.

Dettagli batch

Una richiesta batch è composta da più chiamate API combinate in un'unica richiesta JSON-RPC. Questa sezione descrive in dettaglio la sintassi delle richieste batch, con un esempio nella sezione che segue.

Nota: un insieme di n richieste raggruppate insieme viene conteggiata ai fini del limite di utilizzo come richieste di n, non come una singola richiesta. La richiesta batch viene scomposta in un insieme di richieste prima dell'elaborazione.

Formato di una richiesta batch

La libreria client Java contiene chiamate per creare richieste per ogni chiamata API EMM di Google Play. Ad esempio, per elencare tutte le app installate su un dispositivo, devi utilizzare:

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

È presente un'altra chiamata batch() che può mettere in coda diverse richieste, come mostrato qui:

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() viene chiamato, tutte le richieste in coda vengono inviate contemporaneamente al server come array JSON. Il server applica i parametri di query e le intestazioni (a seconda dei casi) della richiesta esterna a ogni parte, quindi tratta ogni parte come se fosse una richiesta JSON separata.

Risposta a una richiesta batch

Il server esegue ogni richiesta separata e raggruppa i risultati in una singola risposta composta da un singolo array. La libreria client suddivide questa risposta in singole risposte e ognuna viene inviata alla funzione di callback passata a queue(). Il callback è un'interfaccia che definisce un metodo per gli errori e un metodo per il successo. Ad esempio, callback1 verrebbe implementato come istanza di quanto segue:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

Nota: il server potrebbe effettuare le chiamate in qualsiasi ordine, quindi non fare affidamento sulla ricezione dei risultati nell'ordine specificato nella richiesta. Se vuoi assicurarti che due chiamate avvengano in un determinato ordine, non puoi inviarle in un'unica richiesta; invia la prima richiesta da sola e attendi una risposta prima di inviare la seconda.

Esempio di richiesta batch

L'esempio seguente mostra come elencare tutte le app installate su tutti i dispositivi di un determinato utente. Le prime chiamate vengono utilizzate per ottenere l'ID dell'azienda e dell'utente e, di conseguenza, devono essere eseguite in sequenza. Dopo aver ottenuto tutti gli ID dispositivo con enterprise.devices().list(), possiamo effettuare una richiesta batch per recuperare contemporaneamente tutte le applicazioni su tutti i dispositivi dell'utente.

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