Como se describe en el artículo Descripción general de los Servicios de Google Play, los SDKs con la tecnología de los Servicios de Google Play cuentan con la compatibilidad de los servicios integrados en el dispositivo en dispositivos Android certificados por Google. Para conservar el almacenamiento y la memoria en toda la flota de dispositivos, algunos servicios se proporcionan como módulos que se instalan a pedido cuando tu app requiere la funcionalidad relevante. Por ejemplo, ML Kit proporciona esta opción cuando se usan modelos en los Servicios de Google Play.
En la mayoría de los casos, el SDK de Servicios de Google Play descarga e instala los módulos necesarios automáticamente cuando tu app usa una API que los requiere. Sin embargo, es posible que desees tener más control sobre el proceso, por ejemplo, cuando desees mejorar la experiencia del usuario instalando el módulo con anticipación.
La API de ModuleInstallClient
te permite hacer lo siguiente:
- Comprueba si los módulos ya están instalados en el dispositivo.
- Solicita la instalación de los módulos.
- Supervisa el progreso de la instalación.
- Controla los errores durante el proceso de instalación.
En esta guía, se muestra cómo usar ModuleInstallClient
para administrar módulos en tu
app. Ten en cuenta que los siguientes fragmentos de código usan el
SDK de TensorFlow Lite
(play-services-tflite-java
) como ejemplo, pero estos pasos se aplican a
cualquier biblioteca que esté integrada con OptionalModuleApi
.
Antes de comenzar
Para preparar tu app, completa los pasos que se indican en las siguientes secciones.
Requisitos previos de la app
Asegúrate de que el archivo de compilación de tu app use los siguientes valores:
- Una
minSdkVersion
de23
o más
Cómo configurar tu app
En el archivo
settings.gradle
de nivel superior, incluye el repositorio de Maven de Google y el repositorio central de Maven dentro del bloquedependencyResolutionManagement
:dependencyResolutionManagement { repositories { google() mavenCentral() } }
En el archivo de compilación de Gradle de tu módulo (generalmente
app/build.gradle
), agrega las dependencias de los Servicios de Google Play paraplay-services-base
yplay-services-tflite-java
:dependencies { implementation 'com.google.android.gms:play-services-base:18.7.0' implementation 'com.google.android.gms:play-services-tflite-java:16.4.0' }
Cómo comprobar si hay módulos disponibles
Antes de intentar instalar un módulo, puedes verificar si ya está instalado en el dispositivo. Esto te ayuda a evitar solicitudes de instalación innecesarias.
Obtén una instancia de
ModuleInstallClient
:Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
Java
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
Verifica la disponibilidad de un módulo con su
OptionalModuleApi
. El SDK de Servicios de Google Play que usas proporciona esta API.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... }
Java
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… });
Cómo solicitar una instalación diferida
Si no necesitas el módulo de inmediato, puedes solicitar una instalación diferida. Esto permite que los Servicios de Google Play instalen el módulo en segundo plano, posiblemente cuando el dispositivo esté inactivo y conectado a Wi-Fi.
Obtén una instancia de
ModuleInstallClient
:Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
Java
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
Envía la solicitud diferida:
Kotlin
val optionalModuleApi = TfLite.getClient(context) moduleInstallClient.deferredInstall(optionalModuleApi)
Java
OptionalModuleApi optionalModuleApi = TfLite.getClient(context); moduleInstallClient.deferredInstall(optionalModuleApi);
Cómo solicitar una instalación de módulo urgente
Si tu app necesita el módulo de inmediato, puedes solicitar una instalación urgente. Se intentará instalar el módulo lo más rápido posible, incluso si eso significa usar datos móviles.
Obtén una instancia de
ModuleInstallClient
:Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
Java
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
(Opcional) Crea un
InstallStatusListener
para supervisar el progreso de la instalación.Si quieres mostrar el progreso de la descarga en la IU de tu app (por ejemplo, con una barra de progreso), puedes crear un
InstallStatusListener
para recibir actualizaciones.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()
Java
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();
Configura
ModuleInstallRequest
y agregaOptionalModuleApi
a la solicitud: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()
Java
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();
Envía la solicitud de instalación:
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… }
Java
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... });
Prueba tu app con FakeModuleInstallClient
Los SDKs de Servicios de Google Play proporcionan FakeModuleInstallClient
para permitirte
simular los resultados de las APIs de instalación de módulos en pruebas con la inserción de
dependencias. Esto te ayuda a probar el comportamiento de tu app en diferentes situaciones sin necesidad de implementarla en un dispositivo real.
Requisitos previos de la app
Configura tu app para usar el framework de inserción de dependencias de Hilt.
Reemplaza ModuleInstallClient
por FakeModuleInstallClient
en la prueba
Para usar FakeModuleInstallClient
en tus pruebas, debes reemplazar la vinculación ModuleInstallClient
con la implementación falsa.
Agrega la dependencia:
En el archivo de compilación de Gradle de tu módulo (generalmente
app/build.gradle
), agrega las dependencias de los Servicios de Google Play paraplay-services-base-testing
en tu prueba.dependencies { // other dependencies... testImplementation 'com.google.android.gms:play-services-base-testing:16.1.0' }
Crea un módulo de Hilt para proporcionar
ModuleInstallClient
:Kotlin
@Module @InstallIn(ActivityComponent::class) object ModuleInstallModule { @Provides fun provideModuleInstallClient( @ActivityContext context: Context ): ModuleInstallClient = ModuleInstall.getClient(context) }
Java
@Module @InstallIn(ActivityComponent.class) public class ModuleInstallModule { @Provides public static ModuleInstallClient provideModuleInstallClient( @ActivityContext Context context) { return ModuleInstall.getClient(context); } }
Inserta el
ModuleInstallClient
en la actividad:Kotlin
@AndroidEntryPoint class MyActivity: AppCompatActivity() { @Inject lateinit var moduleInstallClient: ModuleInstallClient ... }
Java
@AndroidEntryPoint public class MyActivity extends AppCompatActivity { @Inject ModuleInstallClient moduleInstallClient; ... }
Reemplaza la vinculación en la prueba:
Kotlin
@UninstallModules(ModuleInstallModule::class) @HiltAndroidTest class MyActivityTest { ... private val context:Context = ApplicationProvider.getApplicationContext() private val fakeModuleInstallClient = FakeModuleInstallClient(context) @BindValue @JvmField val moduleInstallClient: ModuleInstallClient = fakeModuleInstallClient ... }
Java
@UninstallModules(ModuleInstallModule.class) @HiltAndroidTest class MyActivityTest { ... private static final Context context = ApplicationProvider.getApplicationContext(); private final FakeModuleInstallClient fakeModuleInstallClient = new FakeModuleInstallClient(context); @BindValue ModuleInstallClient moduleInstallClient = fakeModuleInstallClient; ... }
Simula diferentes situaciones
Con FakeModuleInstallClient
, puedes simular diferentes situaciones, como las siguientes:
- Los módulos ya están instalados.
- Los módulos no están disponibles en el dispositivo.
- Falla el proceso de instalación.
- La solicitud de instalación diferida se realiza correctamente o falla.
- La solicitud de instalación urgente se realiza correctamente o falla.
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... }
Java
@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... }
Simula el resultado de una solicitud de instalación diferida
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... }
Java
@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... }
Simula el resultado de una solicitud de instalación urgente
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... }