שליחת בקשות בכמות גדולה

במסמך הזה מוסבר איך לאחד שיחות API כדי לצמצם את מספר החיבורים שהלקוח צריך לבצע.

מסמך זה עוסק באופן ספציפי בבקשת אצווה באמצעות ספריית הלקוח של Java. דוגמה בסיסית זמינה גם בספריית הלקוח של ממשק API של Google API עבור .NET . מערכת האצווה עבור ממשק ה-API של Google Play לניהול הניידות בארגון משתמשת באותו תחביר של HTTP כמו מערכת עיבוד האצווה של OData.

סקירה כללית

כל בקשה שהלקוח שולח דרך Google Play EMM API מובילה לסכום מסוים של תקורה. ממשק ה-API של Google Play לניהול הניידות בארגון תומך באצווה, כדי לאפשר ללקוח שלך להכניס מספר קריאות ל-API לבקשה אחת.

הנה כמה דוגמאות למצבים שבהם כדאי להשתמש באצוות:

  • הדומיין נרשם עכשיו וכרגע יש לו הרבה נתונים להעלאה.
  • משתמש ביצע שינויים בנתונים בזמן שהאפליקציה לא הייתה מחוברת לאינטרנט, כך שהאפליקציה צריכה לסנכרן כמות גדולה של נתונים מקומיים עם השרת.

במקרים כאלה, במקום לשלוח כל שיחה בנפרד, אפשר לקבץ אותם יחד לבקשה אחת. תוכלו אפילו לקבץ בקשות עבור משתמשים מרובים או עבור ממשקי API מרובים של Google.

עם זאת, אתה מוגבל ל-1000 שיחות בבקשת אצווה אחת. אם צריך לבצע יותר שיחות מאותו מספר, ניתן להשתמש במספר בקשות אצווה.

פרטי אצווה

בקשת אצווה מורכבת ממספר קריאות ל-API המשולבות בבקשת JSON-RPC אחת. הסעיף הזה מתאר בפירוט את התחביר של בקשת האצווה, עם דוגמה בקטע הבא.

הערה: קבוצה של בקשות n המקובצות יחד נספרת כחלק ממגבלת השימוש שלך כבקשות מ-n, ולא כבקשה אחת. בקשת האצווה מחולקת לקבוצת בקשות לפני העיבוד.

פורמט של בקשה לקיבוץ

ספריית הלקוח של Java מכילה קריאות ליצירת בקשות עבור כל קריאה ל-API של Google Play לניהול הניידות בארגון. למשל, כדי לרשום את כל האפליקציות המותקנות במכשיר, צריך להשתמש באפשרויות הבאות:

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

יש שיחת batch() נוספת שיכולה להוסיף כמה בקשות לתור, כפי שמוצג כאן:

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();
כשמתבצעת קריאה ל-batchRequest.execute(), כל הבקשות שנמצאות בתור נשלחות בבת אחת לשרת כמערך JSON. השרת מחיל את פרמטרי השאילתה והכותרות של הבקשה החיצונית (לפי הצורך) על כל חלק, ולאחר מכן מתייחס לכל חלק כאילו יש בקשת JSON נפרדת.

תגובה לבקשת אצווה

השרת מבצע כל בקשה נפרדת, ומקבץ את התוצאה לתגובה יחידה הבנויה ממערך אחד. ספריית הלקוח מפצלת את התגובה הזו לתגובות ספציפיות, וכל תגובה נשלחת לפונקציית הקריאה החוזרת המועברת אל queue(). הקריאה החוזרת (callback) היא ממשק שמגדיר שיטה לכישלון ושיטה להצלחה. לדוגמה, callback1 יוטמע כמופע של:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

הערה: השרת עשוי לבצע את השיחות בכל סדר, לכן אין להסתמך על קבלת תוצאות בסדר שנקבע בבקשה. אם רוצים להבטיח ששתי שיחות יתבצעו בסדר מסוים, לא ניתן יהיה לשלוח אותן בבקשה אחת. במקום זאת, יש לשלוח את הבקשה הראשונה לבד, ולהמתין לתגובה לפני שליחת הבקשה השנייה.

דוגמה לבקשת אצווה

הדוגמה הבאה מראה איך לרשום את כל האפליקציות המותקנות בכל מכשירי המשתמשים. השיחות הראשונות משמשות להשגת המזהה של הארגון ושל המשתמש, ובהתאם לכך יש לבצע אותן ברצף. אחרי שמשיגים את כל מזהי המכשירים באמצעות enterprise.devices().list(), אפשר לשלוח בקשה אחת לאחזור בכל האפליקציות בכל המכשירים של המשתמש בבת אחת.

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