주문형 Google Play 서비스 모듈의 사용 가능 여부 관리

Google Play 서비스 개요 도움말에 설명된 대로 Google Play 서비스에서 제공하는 SDK는 Google 인증 Android 기기의 기기 내 서비스로 지원됩니다. 전체 기기에서 저장공간과 메모리를 절약하기 위해 일부 서비스는 앱에 관련 기능이 필요한 경우 온디맨드로 설치되는 모듈로 제공됩니다. 예를 들어 Google Play 서비스에서 모델을 사용하는 경우 ML Kit에서 이 옵션을 제공합니다.

대부분의 경우 앱에서 필요한 API를 사용하면 Google Play 서비스 SDK가 필요한 모듈을 자동으로 다운로드하고 설치합니다. 하지만 모듈을 미리 설치하여 사용자 환경을 개선하려는 경우와 같이 프로세스를 더 세부적으로 제어하고 싶을 수 있습니다.

ModuleInstallClient API를 사용하면 다음 작업을 할 수 있습니다.

  • 모듈이 기기에 이미 설치되어 있는지 확인합니다.
  • 모듈 설치를 요청합니다.
  • 설치 진행 상황을 모니터링합니다.
  • 설치 과정 중 오류를 처리합니다.

이 가이드에서는 ModuleInstallClient를 사용하여 앱의 모듈을 관리하는 방법을 보여줍니다. 다음 코드 스니펫에서는 TensorFlow Lite SDK(play-services-tflite-java)를 예로 사용하지만 이러한 단계는 OptionalModuleApi와 통합된 모든 라이브러리에 적용할 수 있습니다.

시작하기 전에

앱을 준비하려면 다음 섹션의 단계를 완료합니다.

앱 기본 요건

앱의 빌드 파일이 다음 값을 사용하는지 확인합니다.

  • minSdkVersion 23 이상

앱 구성

  1. 최상위 수준 settings.gradle 파일의 dependencyResolutionManagement 블록 내에 Google의 Maven 저장소Maven 중앙 저장소를 포함합니다.

    dependencyResolutionManagement {     repositories {         google()         mavenCentral()     } } 
  2. 모듈의 Gradle 빌드 파일 (일반적으로 app/build.gradle)에서 play-services-baseplay-services-tflite-java의 Google Play 서비스 종속 항목을 추가합니다.

    dependencies {   implementation 'com.google.android.gms:play-services-base:18.9.0'   implementation 'com.google.android.gms:play-services-tflite-java:16.4.0' } 

모듈이 사용 가능한지 확인

모듈을 설치하기 전에 기기에 이미 설치되어 있는지 확인할 수 있습니다. 이렇게 하면 불필요한 설치 요청을 방지할 수 있습니다.

  1. ModuleInstallClient의 인스턴스를 가져옵니다.

    Kotlin

    val moduleInstallClient = ModuleInstall.getClient(context)

    자바

    ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
  2. 모듈의 OptionalModuleApi를 사용하여 모듈의 사용 가능 여부를 확인합니다. 이 API는 사용 중인 Google Play 서비스 SDK에서 제공합니다.

    Kotlin

    val optionalModuleApi = TfLite.getClient(context) moduleInstallClient   .areModulesAvailable(optionalModuleApi)   .addOnSuccessListener {     if (it.areModulesAvailable()) {       // Modules are present on the device...     } else {       // Modules are not present on the device...     }   }   .addOnFailureListener {     // Handle failure...   }

    자바

    OptionalModuleApi optionalModuleApi = TfLite.getClient(context); moduleInstallClient     .areModulesAvailable(optionalModuleApi)     .addOnSuccessListener(         response -> {           if (response.areModulesAvailable()) {             // Modules are present on the device...           } else {             // Modules are not present on the device...           }         })     .addOnFailureListener(         e -> {           // Handle failure…         });

지연된 설치 요청

모듈이 즉시 필요하지 않은 경우 지연된 설치를 요청할 수 있습니다. 이를 통해 Google Play 서비스는 기기가 유휴 상태이고 Wi-Fi에 연결되어 있을 때 백그라운드에서 모듈을 설치할 수 있습니다.

  1. ModuleInstallClient의 인스턴스를 가져옵니다.

    Kotlin

    val moduleInstallClient = ModuleInstall.getClient(context)

    자바

    ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
  2. 지연된 요청을 전송합니다.

    Kotlin

    val optionalModuleApi = TfLite.getClient(context) moduleInstallClient.deferredInstall(optionalModuleApi)

    자바

    OptionalModuleApi optionalModuleApi = TfLite.getClient(context); moduleInstallClient.deferredInstall(optionalModuleApi);

긴급 모듈 설치 요청

앱에 모듈이 즉시 필요한 경우 긴급 설치를 요청할 수 있습니다. 이렇게 하면 모바일 데이터를 사용하더라도 최대한 빨리 모듈을 설치하려고 시도합니다.

  1. ModuleInstallClient의 인스턴스를 가져옵니다.

    Kotlin

    val moduleInstallClient = ModuleInstall.getClient(context)

    자바

    ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
  2. (선택사항) 설치 진행 상황을 모니터링하는 InstallStatusListener를 만듭니다.

    앱의 UI에 다운로드 진행률을 표시하려면 (예: 진행률 표시줄 사용) 업데이트를 수신하는 InstallStatusListener를 만들면 됩니다.

    Kotlin

    inner class ModuleInstallProgressListener : InstallStatusListener {   override fun onInstallStatusUpdated(update: ModuleInstallStatusUpdate) {     // Progress info is only set when modules are in the progress of downloading.     update.progressInfo?.let {       val progress = (it.bytesDownloaded * 100 / it.totalBytesToDownload).toInt()       // Set the progress for the progress bar.       progressBar.setProgress(progress)     }      if (isTerminateState(update.installState)) {       moduleInstallClient.unregisterListener(this)     }   }    fun isTerminateState(@InstallState state: Int): Boolean {     return state == STATE_CANCELED || state == STATE_COMPLETED || state == STATE_FAILED   } }  val listener = ModuleInstallProgressListener()

    자바

    static final class ModuleInstallProgressListener implements InstallStatusListener {     @Override     public void onInstallStatusUpdated(ModuleInstallStatusUpdate update) {       ProgressInfo progressInfo = update.getProgressInfo();       // Progress info is only set when modules are in the progress of downloading.       if (progressInfo != null) {         int progress =             (int)                 (progressInfo.getBytesDownloaded() * 100 / progressInfo.getTotalBytesToDownload());         // Set the progress for the progress bar.         progressBar.setProgress(progress);       }       // Handle failure status maybe…        // Unregister listener when there are no more install status updates.       if (isTerminateState(update.getInstallState())) {          moduleInstallClient.unregisterListener(this);       }     }      public boolean isTerminateState(@InstallState int state) {       return state == STATE_CANCELED || state == STATE_COMPLETED || state == STATE_FAILED;     }   }  InstallStatusListener listener = new ModuleInstallProgressListener();
  3. ModuleInstallRequest를 구성하고 OptionalModuleApi를 요청에 추가합니다.

    Kotlin

    val optionalModuleApi = TfLite.getClient(context) val moduleInstallRequest =   ModuleInstallRequest.newBuilder()     .addApi(optionalModuleApi)     // Add more APIs if you would like to request multiple modules.     // .addApi(...)     // Set the listener if you need to monitor the download progress.     // .setListener(listener)     .build()

    자바

    OptionalModuleApi optionalModuleApi = TfLite.getClient(context); ModuleInstallRequest moduleInstallRequest =     ModuleInstallRequest.newBuilder()         .addApi(optionalModuleApi)         // Add more API if you would like to request multiple modules         //.addApi(...)         // Set the listener if you need to monitor the download progress         //.setListener(listener)         .build();
  4. 설치 요청을 전송합니다.

    Kotlin

    moduleInstallClient   .installModules(moduleInstallRequest)   .addOnSuccessListener {     if (it.areModulesAlreadyInstalled()) {       // Modules are already installed when the request is sent.     }     // The install request has been sent successfully. This does not mean     // the installation is completed. To monitor the install status, set an     // InstallStatusListener to the ModuleInstallRequest.   }   .addOnFailureListener {     // Handle failure…   }

    자바

    moduleInstallClient.installModules(moduleInstallRequest)     .addOnSuccessListener(         response -> {           if (response.areModulesAlreadyInstalled()) {             // Modules are already installed when the request is sent.           }           // The install request has been sent successfully. This does not           // mean the installation is completed. To monitor the install           // status, set an InstallStatusListener to the           // ModuleInstallRequest.         })     .addOnFailureListener(         e -> {           // Handle failure...         });

FakeModuleInstallClient로 앱 테스트

Google Play 서비스 SDK는 종속 항목 삽입을 사용하여 테스트에서 모듈 설치 API의 결과를 시뮬레이션할 수 있도록 FakeModuleInstallClient를 제공합니다. 이를 통해 실제 기기에 앱을 배포하지 않고도 다양한 시나리오에서 앱의 동작을 테스트할 수 있습니다.

앱 기본 요건

Hilt 종속 항목 삽입 프레임워크를 사용하도록 앱을 구성합니다.

테스트에서 ModuleInstallClientFakeModuleInstallClient로 대체

테스트에서 FakeModuleInstallClient를 사용하려면 ModuleInstallClient 바인딩을 가짜 구현으로 대체해야 합니다.

  1. 종속 항목을 추가합니다.

    모듈의 Gradle 빌드 파일 (일반적으로 app/build.gradle)에서 테스트에 play-services-base-testing의 Google Play 서비스 종속 항목을 추가합니다.

      dependencies {     // other dependencies...      testImplementation 'com.google.android.gms:play-services-base-testing:16.2.0'   } 
  2. ModuleInstallClient를 제공하는 Hilt 모듈을 만듭니다.

    Kotlin

    @Module @InstallIn(ActivityComponent::class) object ModuleInstallModule {    @Provides   fun provideModuleInstallClient(     @ActivityContext context: Context   ): ModuleInstallClient = ModuleInstall.getClient(context) }

    자바

    @Module @InstallIn(ActivityComponent.class) public class ModuleInstallModule {   @Provides   public static ModuleInstallClient provideModuleInstallClient(     @ActivityContext Context context) {     return ModuleInstall.getClient(context);   } }
  3. 활동에 ModuleInstallClient 삽입

    Kotlin

    @AndroidEntryPoint class MyActivity: AppCompatActivity() {   @Inject lateinit var moduleInstallClient: ModuleInstallClient    ... }

    자바

    @AndroidEntryPoint public class MyActivity extends AppCompatActivity {   @Inject ModuleInstallClient moduleInstallClient;    ... }
  4. 테스트에서 바인딩을 대체합니다.

    Kotlin

    @UninstallModules(ModuleInstallModule::class) @HiltAndroidTest class MyActivityTest {   ...   private val context:Context = ApplicationProvider.getApplicationContext()   private val fakeModuleInstallClient = FakeModuleInstallClient(context)   @BindValue @JvmField   val moduleInstallClient: ModuleInstallClient = fakeModuleInstallClient    ... }

    자바

    @UninstallModules(ModuleInstallModule.class) @HiltAndroidTest class MyActivityTest {   ...   private static final Context context = ApplicationProvider.getApplicationContext();   private final FakeModuleInstallClient fakeModuleInstallClient = new FakeModuleInstallClient(context);   @BindValue ModuleInstallClient moduleInstallClient = fakeModuleInstallClient;    ... }

다양한 시나리오 시뮬레이션

FakeModuleInstallClient를 사용하면 다음과 같은 다양한 시나리오를 시뮬레이션할 수 있습니다.

  • 모듈이 이미 설치되어 있습니다.
  • 기기에서 모듈을 사용할 수 없습니다.
  • 설치 프로세스가 실패합니다.
  • 지연된 설치 요청이 성공하거나 실패합니다.
  • 긴급 설치 요청이 성공하거나 실패합니다.

Kotlin

@Test fun checkAvailability_available() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset()    val availableModule = TfLite.getClient(context)   fakeModuleInstallClient.setInstalledModules(api)    // Verify the case where modules are already available... }  @Test fun checkAvailability_unavailable() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset()    // Do not set any installed modules in the test.    // Verify the case where modules unavailable on device... }  @Test fun checkAvailability_failed() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset()    fakeModuleInstallClient.setModulesAvailabilityTask(Tasks.forException(RuntimeException()))    // Verify the case where an RuntimeException happened when trying to get module's availability... }

자바

@Test public void checkAvailability_available() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    OptionalModuleApi optionalModuleApi = TfLite.getClient(context);   fakeModuleInstallClient.setInstalledModules(api);    // Verify the case where modules are already available... }  @Test public void checkAvailability_unavailable() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    // Do not set any installed modules in the test.    // Verify the case where modules unavailable on device... }  @Test public void checkAvailability_failed() {   fakeModuleInstallClient.setModulesAvailabilityTask(Tasks.forException(new RuntimeException()));    // Verify the case where an RuntimeException happened when trying to get module's availability... }

지연된 설치 요청의 결과 시뮬레이션

Kotlin

@Test fun deferredInstall_success() {   fakeModuleInstallClient.setDeferredInstallTask(Tasks.forResult(null))    // Verify the case where the deferred install request has been sent successfully... }  @Test fun deferredInstall_failed() {   fakeModuleInstallClient.setDeferredInstallTask(Tasks.forException(RuntimeException()))    // Verify the case where an RuntimeException happened when trying to send the deferred install request... }

자바

@Test public void deferredInstall_success() {   fakeModuleInstallClient.setDeferredInstallTask(Tasks.forResult(null));    // Verify the case where the deferred install request has been sent successfully... }  @Test public void deferredInstall_failed() {   fakeModuleInstallClient.setDeferredInstallTask(Tasks.forException(new RuntimeException()));    // Verify the case where an RuntimeException happened when trying to send the deferred install request... }

긴급 설치 요청의 결과 시뮬레이션

Kotlin

@Test fun installModules_alreadyExist() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    OptionalModuleApi optionalModuleApi = TfLite.getClient(context);   fakeModuleInstallClient.setInstalledModules(api);    // Verify the case where the modules already exist when sending the install request... }  @Test fun installModules_withoutListener() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    // Verify the case where the urgent install request has been sent successfully... }  @Test fun installModules_withListener() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    // Generates a ModuleInstallResponse and set it as the result for installModules().   val moduleInstallResponse = FakeModuleInstallUtil.generateModuleInstallResponse()   fakeModuleInstallClient.setInstallModulesTask(Tasks.forResult(moduleInstallResponse))    // Verify the case where the urgent install request has been sent successfully...    // Generates some fake ModuleInstallStatusUpdate and send it to listener.   val update = FakeModuleInstallUtil.createModuleInstallStatusUpdate(     moduleInstallResponse.sessionId, STATE_COMPLETED)   fakeModuleInstallClient.sendInstallUpdates(listOf(update))    // Verify the corresponding updates are handled correctly... }  @Test fun installModules_failed() {   fakeModuleInstallClient.setInstallModulesTask(Tasks.forException(RuntimeException()))    // Verify the case where an RuntimeException happened when trying to send the urgent install request... }

Java

@Test public void installModules_alreadyExist() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    OptionalModuleApi optionalModuleApi = TfLite.getClient(context);   fakeModuleInstallClient.setInstalledModules(api);    // Verify the case where the modules already exist when sending the install request... }  @Test public void installModules_withoutListener() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    // Verify the case where the urgent install request has been sent successfully... }  @Test public void installModules_withListener() {   // Reset any previously installed modules.   fakeModuleInstallClient.reset();    // Generates a ModuleInstallResponse and set it as the result for installModules().   ModuleInstallResponse moduleInstallResponse =       FakeModuleInstallUtil.generateModuleInstallResponse();   fakeModuleInstallClient.setInstallModulesTask(Tasks.forResult(moduleInstallResponse));    // Verify the case where the urgent install request has been sent successfully...    // Generates some fake ModuleInstallStatusUpdate and send it to listener.   ModuleInstallStatusUpdate update = FakeModuleInstallUtil.createModuleInstallStatusUpdate(       moduleInstallResponse.getSessionId(), STATE_COMPLETED);   fakeModuleInstallClient.sendInstallUpdates(ImmutableList.of(update));    // Verify the corresponding updates are handled correctly... }  @Test public void installModules_failed() {   fakeModuleInstallClient.setInstallModulesTask(Tasks.forException(new RuntimeException()));    // Verify the case where an RuntimeException happened when trying to send the urgent install request... }