傳送批次要求

本文說明如何批次處理 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();
  }
}