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

Tài liệu này cho biết cách nhóm các lệnh gọi API lại 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 giới thiệu cụ thể cách tạo một yêu cầu hàng loạt bằng cách sử dụng thư viện ứng dụng Java. Một ví dụ cơ bản cũng có trong Thư viện ứng dụng .NET cho API của Google. Hệ thống hàng loạt dành cho API EMM của Google Play sử dụng cú pháp HTTP giống như hệ thống xử lý hàng loạt OData.

Tổng quan

Mỗi yêu cầu mà ứng dụng của bạn thực hiện thông qua API Google Play EMM sẽ dẫn đến một mức hao tổn nhất định. API EMM của Google Play hỗ trợ việc tạo yêu cầu hàng loạt, để cho phép ứng dụng của bạn thực hiện một số 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 tạo lô:

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

Trong 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 các cuộc gọi lại với nhau 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 gửi 1000 cuộc 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 cuộc 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 được kết hợp thành một yêu cầu JSON-RPC. Phần này mô tả chi tiết cú pháp yêu cầu hàng loạt, kèm theo ví dụ trong phần sau.

Lưu ý: Một nhóm các yêu cầu n được nhóm lại với nhau sẽ được tính vào hạn mức sử dụng của bạn là n yêu cầu, chứ không phải là một yêu cầu. Yêu cầu hàng loạt được tách thành một nhóm các 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 Google Play EMM. Ví dụ: để liệt kê tất cả ứng dụng đã cài đặt trên thiết bị, bạn sẽ sử dụng:

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

Có một lệnh gọi batch() khác có thể đưa vào hàng đợi nhiều yêu cầu, như được hiển thị ở đâ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ả các yêu cầu đã xếp hàng đợi sẽ được gửi cùng lúc đến máy chủ dưới dạng mảng JSON. Máy chủ áp dụng các thông số truy vấn và tiêu đề 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 được tạo thành một mảng duy nhất. Thư viện ứng dụng phân chia phản hồi này thành các phản hồi riêng lẻ và mỗi phản hồi được gửi đến hàm callback được truyền vào queue(). Lệnh gọi lại là một giao diện xác định phương thức xử lý lỗi và phương thức thành công. Ví dụ: callback1 sẽ được triển khai dưới dạng phiên bản 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 các lệnh gọi của bạn theo thứ tự bất kỳ, vì vậy, đừng dựa vào việc nhận kết quả theo thứ tự đã chỉ định trong yêu cầu của bạn. Nếu muốn đảm bảo rằng hai lệnh gọi xảy ra theo một thứ tự nhất định, bạn không thể gửi các yêu cầu đó trong một yêu cầu duy nhất; thay vào đó, hãy gửi yêu cầu đầu tiên và đợ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 minh họa cách liệt kê tất cả ứng dụng đã cài đặt trên tất cả các thiết bị của người dùng nhất định. Các lệnh gọi đầu tiên được dùng để lấy mã doanh nghiệp và của người dùng, sau đó phải được thực thi theo 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 một yêu cầu truy xuất 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();
  }
}