傳送批次要求

本文說明如何批次處理 API 呼叫,以減少用戶端必須建立的連線數量。

本文件特別說明如何使用 Java 用戶端程式庫提出批次要求。.NET 適用的 Google API 用戶端程式庫也提供基本範例。Google Play EMM API 的批次系統使用的 HTTP 語法與 OData 批次處理系統相同。

總覽

客戶透過 Google Play EMM API 發出的每個要求都會產生一定程度的負擔。Google Play EMM API 支援批次作業,可讓您的用戶端發出多個 API 呼叫。

以下列舉幾種建議使用批次操作的情況:

  • 某個網域剛註冊完成,且已有許多資料可供上傳。
  • 使用者在離線應用程式時變更了資料,因此您的應用程式需要將大量的本機資料與伺服器同步。

如果發生這種情況,您不必個別傳送每個呼叫,而是將這些呼叫歸為同一個請求。您甚至可以針對多位使用者或多個 Google API 執行要求。

不過,單一批次要求最多只能呼叫 1,000 次。如果您需要進行呼叫,請使用多個批次要求。

批次詳細資料

批次要求是由多個 API 呼叫組成,然後合併為一個 JSON-RPC 要求。本節將詳細說明批次要求語法,請參閱下方的範例

注意:系統會將一組「n」批次處理成一個用量上限,並計入「n」要求的用量,而不是一個要求。批次要求會先分割成一組要求,然後再進行處理。

批次要求的格式

Java 用戶端程式庫包含呼叫,用於為每個 Google Play EMM API 呼叫建立要求。舉例來說,如要列出裝置上安裝的所有應用程式,請使用以下程式碼:

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() 的回呼函式。回呼是定義失敗方法和成功方法的介面。舉例來說,系統會將 callback1 實作為下列的執行個體:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

注意:伺服器可能會按任何順序執行呼叫,因此請勿依照要求中指定的順序接收結果。如要確保以特定順序執行兩次呼叫,就無法在單一要求中傳送這些呼叫;請改為傳送第一個要求,然後等待回應,再傳送第二個要求。

批次要求範例

以下範例說明如何列出在特定使用者裝置上安裝的所有應用程式。第一次呼叫是用來取得企業和使用者的 ID,因此必須依序執行。使用 enterprise.devices().list() 取得所有裝置 ID 後,即可執行批次要求,一次擷取使用者裝置上所有應用程式的所有應用程式。

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