Gửi yêu cầu hàng loạt

Tài liệu này trình bày cách phân nhóm các lệnh gọi API với nhau để giảm số lượng kết nối mà ứng dụng của bạn phải thực hiện.

Tài liệu này trình bày cụ thể về việc thực hiện yêu cầu hàng loạt bằng thư viện ứng dụng Java. Ví dụ cơ bản cũng có trong Thư viện ứng dụng API của Google dành cho .NET. Hệ thống lô cho API EMM của Google Play sử dụng cùng một HTTP là hệ thống xử lý theo lô OData.

Tổng quan

Mỗi yêu cầu mà ứng dụng của bạn đưa ra thông qua API EMM của Google Play đều dẫn đến một mức hao tổn nhất định. API EMM của Google Play hỗ trợ tính năng phân lô để cho phép ứng dụng của bạn đặt nhiều lệnh gọi API vào một yêu cầu duy nhất.

Sau đây là một số ví dụ về các trường hợp mà bạn có thể muốn sử dụng phân lô:

  • Miền vừa được đăng ký và hiện có nhiều dữ liệu để tải lên.
  • Người dùng đã thực hiện các thay đổi đối với dữ liệu trong khi ứng dụng của bạn không kết nối mạng, vì vậy, ứng dụng của bạn cần đồng bộ hoá nhiều dữ liệu cục bộ với máy chủ.

Trong những trường hợp như vậy, thay vì gửi riêng từng cuộc gọi, bạn có thể nhóm lại các cuộc gọi đó thành một yêu cầu duy nhất. Thậm chí bạn có thể nhóm các yêu cầu cho nhiều người dùng hoặc cho nhiều API của Google.

Tuy nhiên, bạn chỉ được có tối đa 1.000 lệnh gọi trong một yêu cầu hàng loạt. Nếu bạn cần thực hiện nhiều lệnh gọi hơn số lượng đó, hãy sử dụng nhiều yêu cầu hàng loạt.

Chi tiết gói

Một yêu cầu hàng loạt bao gồm nhiều lệnh gọi API kết hợp thành một yêu cầu JSON-RPC. Phần này mô tả chi tiết về cú pháp yêu cầu hàng loạt, kèm theo ví dụ trong phần sau.

Lưu ý: Một tập hợp n yêu cầu được nhóm cùng nhau sẽ được tính vào hạn mức sử dụng của bạn dưới dạng n yêu cầu chứ không phải một yêu cầu. Yêu cầu hàng loạt được tách thành một nhóm yêu cầu trước khi xử lý.

Định dạng của một yêu cầu hàng loạt

Thư viện ứng dụng Java chứa các lệnh gọi để tạo yêu cầu cho từng lệnh gọi API EMM của Google Play. Ví dụ: để liệt kê tất cả các ứng dụng đã cài đặt trên một thiết bị, bạn sẽ sử dụng lệnh sau:

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

Còn có một lệnh gọi batch() khác có thể đưa một số yêu cầu vào hàng đợi, như bạn thấy dưới đây:

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();
Khi batchRequest.execute() được gọi, tất cả yêu cầu trong hàng đợi sẽ được gửi cùng lúc đến máy chủ dưới dạng một mảng JSON. Máy chủ sẽ áp dụng tiêu đề và tham số truy vấn của yêu cầu bên ngoài (nếu phù hợp) cho từng phần, sau đó xử lý từng phần như thể đó là một yêu cầu JSON riêng biệt.

Phản hồi yêu cầu hàng loạt

Máy chủ thực thi từng yêu cầu riêng biệt và nhóm kết quả thành một phản hồi duy nhất tạo thành một mảng duy nhất. Thư viện ứng dụng chia phản hồi này thành các phản hồi riêng lẻ, rồi gửi mỗi phản hồi này đến hàm callback được truyền đến queue(). Lệnh gọi lại là một giao diện xác định một phương thức để xác định lỗi và một phương thức để thành công. Ví dụ: callback1 sẽ được triển khai dưới dạng một thực thể sau:

private class InstallsCallback implements JsonBatchCallback<InstallsListResponse> {

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

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

Lưu ý: Máy chủ có thể thực hiện lệnh gọi theo thứ tự bất kỳ, vì vậy, đừng dựa vào việc nhận kết quả theo thứ tự được chỉ định trong yêu cầu của bạn. Nếu muốn đảm bảo rằng 2 lệnh gọi xảy ra theo một thứ tự nhất định, bạn không thể gửi các lệnh gọi đó trong một yêu cầu duy nhất; thay vào đó, hãy tự gửi yêu cầu đầu tiên rồi đợi phản hồi trước khi gửi yêu cầu thứ hai.

Ví dụ về yêu cầu hàng loạt

Ví dụ sau đây cho biết cách liệt kê tất cả ứng dụng đã cài đặt trên tất cả thiết bị của một người dùng cụ thể. Các lệnh gọi đầu tiên được dùng để lấy mã nhận dạng của doanh nghiệp và người dùng, theo đó phải được thực thi tuần tự. Sau khi đã lấy tất cả mã thiết bị bằng enterprise.devices().list(), chúng ta có thể thực hiện yêu cầu hàng loạt để truy xuất tất cả ứng dụng trên tất cả thiết bị của người dùng cùng một lúc.

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