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
이상
앱 구성
최상위 수준
settings.gradle
파일의dependencyResolutionManagement
블록 내에 Google Maven 저장소와 Maven 중앙 저장소를 포함합니다.dependencyResolutionManagement { repositories { google() mavenCentral() } }
모듈의 Gradle 빌드 파일 (일반적으로
app/build.gradle
)에서play-services-base
및play-services-tflite-java
의 Google Play 서비스 종속 항목을 추가합니다.dependencies { implementation 'com.google.android.gms:play-services-base:18.7.0' implementation 'com.google.android.gms:play-services-tflite-java:16.4.0' }
모듈을 사용할 수 있는지 확인
모듈을 설치하기 전에 기기에 이미 설치되어 있는지 확인할 수 있습니다. 이렇게 하면 불필요한 설치 요청을 방지할 수 있습니다.
ModuleInstallClient
의 인스턴스를 가져옵니다.Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
자바
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
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에 연결되어 있을 때 백그라운드에서 모듈을 설치할 수 있습니다.
ModuleInstallClient
의 인스턴스를 가져옵니다.Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
자바
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
지연된 요청을 전송합니다.
Kotlin
val optionalModuleApi = TfLite.getClient(context) moduleInstallClient.deferredInstall(optionalModuleApi)
자바
OptionalModuleApi optionalModuleApi = TfLite.getClient(context); moduleInstallClient.deferredInstall(optionalModuleApi);
긴급 모듈 설치 요청
앱에 모듈이 즉시 필요한 경우 긴급 설치를 요청할 수 있습니다. 이렇게 하면 모바일 데이터를 사용해야 하더라도 모듈을 최대한 빨리 설치하려고 시도합니다.
ModuleInstallClient
의 인스턴스를 가져옵니다.Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
자바
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
(선택사항)
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();
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();
설치 요청을 전송합니다.
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 종속 항목 주입 프레임워크를 사용하도록 앱을 구성합니다.
테스트에서 ModuleInstallClient
를 FakeModuleInstallClient
로 바꿉니다.
테스트에서 FakeModuleInstallClient
를 사용하려면 ModuleInstallClient
결합을 가짜 구현으로 대체해야 합니다.
종속 항목을 추가합니다.
모듈의 Gradle 빌드 파일 (일반적으로
app/build.gradle
)에서 테스트에play-services-base-testing
의 Google Play 서비스 종속 항목을 추가합니다.dependencies { // other dependencies... testImplementation 'com.google.android.gms:play-services-base-testing:16.1.0' }
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); } }
활동에
ModuleInstallClient
을(를) 삽입합니다.Kotlin
@AndroidEntryPoint class MyActivity: AppCompatActivity() { @Inject lateinit var moduleInstallClient: ModuleInstallClient ... }
자바
@AndroidEntryPoint public class MyActivity extends AppCompatActivity { @Inject ModuleInstallClient moduleInstallClient; ... }
테스트에서 결합을 바꿉니다.
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... }