Inviare richieste batch

Questo documento mostra come raggruppare le chiamate API per ridurre il numero di connessioni stabilite dal tuo client.

Questo documento riguarda nello specifico l'esecuzione di una richiesta batch tramite la libreria client Java. Un esempio di base è disponibile anche nella libreria client delle API di Google per .NET. Il sistema batch per l'API EMM di Google Play utilizza la stessa sintassi HTTP del sistema di elaborazione batch OData.

Panoramica

Ogni richiesta effettuata dal client tramite l'API EMM di Google Play genera un certo sovraccarico. L'API EMM di Google Play supporta il batch, per consentire al client di inserire più chiamate API in un'unica richiesta.

Di seguito sono riportati alcuni esempi di situazioni in cui è consigliabile utilizzare il raggruppamento:

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

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

Tuttavia, non puoi superare le 1000 chiamate in una singola richiesta in batch. Se devi effettuare più chiamate, utilizza più richieste in batch.

Dettagli batch

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

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

Formato di una richiesta in 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, puoi utilizzare quanto segue:

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

Risposta a una richiesta in batch

Il server esegue ogni richiesta separata e raggruppa il risultato in una singola risposta composta da un singolo array. La libreria client suddivide questa risposta in singole risposte, ciascuna delle quali viene inviata alla funzione di callback passata a queue(). Il callback è un'interfaccia che definisce un metodo per l'esito negativo e un metodo per l'esito positivo. Ad esempio, callback1 viene 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 eseguire le tue chiamate in qualsiasi ordine, quindi non fare affidamento sulla ricezione dei risultati nell'ordine specificato nella richiesta. Se vuoi assicurarti che si verifichino due chiamate in un determinato ordine, non puoi inviarle in una singola richiesta, piuttosto devi inviare la prima richiesta da sola e attendere una risposta prima di inviare la seconda.

Esempio di richiesta in batch

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

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