일괄 요청 전송

이 문서에서는 API 호출을 일괄 처리하여 클라이언트가 수행해야 하는 연결 수를 줄이는 방법을 보여줍니다.

이 문서에서는 특히 Java 클라이언트 라이브러리를 사용하여 일괄 요청을 하는 방법을 다룹니다. .NET용 Google API 클라이언트 라이브러리를 참조하세요. Google Play EMM API의 일괄 시스템은 동일한 HTTP를 사용합니다. 문법을 OData 일괄 처리 시스템으로 사용합니다.

개요

클라이언트가 Google Play EMM API를 통해 요청할 때마다 일정량의 오버헤드가 발생합니다. Google Play EMM API는 일괄 처리를 지원하므로 클라이언트에서 단일 요청에 여러 API 호출을 넣을 수 있습니다.

일괄 처리를 사용할 수 있는 상황의 예는 다음과 같습니다.

  • 도메인이 방금 등록되었으며 업로드할 데이터가 많습니다.
  • 애플리케이션이 오프라인일 때 사용자가 데이터를 변경했으므로 애플리케이션은 많은 로컬 데이터를 서버와 동기화해야 합니다.

이러한 경우 각 호출을 개별적으로 보내는 대신 단일 요청으로 그룹화할 수 있습니다. 여러 사용자 또는 여러 Google API에 대한 요청을 그룹화할 수도 있습니다.

하지만 일괄 요청 하나에 호출 수는 1,000개로 제한됩니다. 이보다 더 많이 호출해야 하는 경우 일괄 요청을 여러 개 사용하세요.

일괄 처리 세부정보

일괄 요청은 하나의 JSON-RPC 요청으로 결합된 여러 API 호출로 구성됩니다. 이 섹션에서는 일괄 요청 문법을 자세히 설명합니다(다음 섹션의 ).

참고: 일괄 처리된 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();
  }
}