Garantire la disponibilità dell'API con ModuleInstallClient

Come descritto nell'articolo Panoramica di Google Play Services, gli SDK basati su Google Play Services sono supportati dai servizi on-device sui dispositivi Android certificati da Google. Per risparmiare spazio di archiviazione e memoria sull'intero parco dispositivi, alcuni servizi vengono installati on demand quando è chiaro che un dispositivo specifico richiede la funzionalità pertinente. Ad esempio, ML Kit offre questa opzione quando si utilizzano modelli in Google Play Services.

Il caso più comune è il download e l'installazione di un servizio (o "modulo") in parallelo a un'app che lo richiede, in base a una dipendenza nell'elemento AndroidManifest.xml dell'SDK. Per un maggiore controllo, le API di installazione dei moduli offrono la possibilità di verificare esplicitamente la disponibilità dei moduli, richiedere l'installazione dei moduli, monitorare lo stato delle richieste e gestire gli errori.

Segui i passaggi riportati di seguito per garantire la disponibilità dell'API con ModuleInstallClient. Tieni presente che gli snippet di codice riportati di seguito utilizzano l'SDK TensorFlow (play-services-tflite-java) come libreria di esempio, ma questi passaggi sono applicabili a qualsiasi libreria integrata in OptionalModuleApi. Questa guida verrà aggiornata con ulteriori informazioni man mano che altri SDK aggiungeranno supporto.

Prima di iniziare

Per preparare l'app, completa i passaggi nelle sezioni seguenti.

Prerequisiti dell'app

Assicurati che il file di build dell'app utilizzi i seguenti valori:

  • A minSdkVersion di 19 o superiore

Configura la tua app

  1. Nel file settings.gradle di primo livello, includi il Repository Maven di Google e il repository centrale Maven all'interno del blocco dependencyResolutionManagement:

    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
        }
    }
    
  2. Nel file di build Gradle del modulo (di solito app/build.gradle), aggiungi le dipendenze di Google Play Services per play-services-base e play-services-tflite-java:

    dependencies {
      implementation 'com.google.android.gms:play-services-base:18.5.0'
      implementation 'com.google.android.gms:play-services-tflite-java:16.2.0-beta02'
    }
    

Verificare la disponibilità dei moduli

  1. Ottieni un'istanza di ModuleInstallClient:

    Kotlin

    val moduleInstallClient = ModuleInstall.getClient(context)
    

    Java

    ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
    
  2. Verifica la disponibilità di un modulo facoltativo utilizzando OptionalModuleApi:

    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…
            });
    

Invia una richiesta di installazione differita

  1. Ottieni un'istanza di ModuleInstallClient:

    Kotlin

    val moduleInstallClient = ModuleInstall.getClient(context)
    

    Java

    ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
    
  2. Invia la richiesta differita:

    Kotlin

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

    Java

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

Invia una richiesta di installazione urgente del modulo

  1. Ottieni un'istanza di ModuleInstallClient:

    Kotlin

    val moduleInstallClient = ModuleInstall.getClient(context)
    

    Java

    ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
    
  2. (Facoltativo) Crea un InstallStatusListener per gestire gli aggiornamenti dello stato dell'installazione.

    Se vuoi monitorare l'avanzamento del download in un'interfaccia utente personalizzata (ad esempio, una barra di avanzamento), puoi creare un InstallStatusListener per ricevere gli aggiornamenti dello stato dell'installazione.

    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();
    
  3. Configura ModuleInstallRequest e aggiungi OptionalModuleApi alla richiesta:

    Kotlin

    val optionalModuleApi = TfLite.getClient(context)
    val moduleInstallRequest =
      ModuleInstallRequest.newBuilder()
        .addApi(optionalModuleApi)
        // Add more APIs if you would like to request multiple optional 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 optional modules
            //.addApi(...)
            // Set the listener if you need to monitor the download progress
            //.setListener(listener)
            .build();
    
  4. Invia la richiesta di installazione:

    Kotlin

    moduleInstallClient
      .installModules(moduleInstallRequest)
      .addOnSuccessListener {
        if (it.areModulesAlreadyInstalled()) {
          // Modules are already installed when the request is sent.
        }
      }
      .addOnFailureListener {
        // Handle failure…
      }
    

    Java

    moduleInstallClient.installModules(moduleInstallRequest)
        .addOnSuccessListener(
            response -> {
              if (response.areModulesAlreadyInstalled()) {
                // Modules are already installed when the request is sent.
              }
            })
        .addOnFailureListener(
            e -> {
              // Handle failure...
            });
    

Test locale con FakeModuleInstallClient

Gli SDK Google Play Services forniscono FakeModuleInstallClient per consentirti di simulare i risultati delle API di installazione di moduli nei test utilizzando l'inserimento delle dipendenze.

Prerequisiti dell'app

Configura l'app per utilizzare il framework di inserimento delle dipendenze di Hilt.

Sostituisci ModuleInstallClient con FakeModuleInstallClient nel test

  1. Aggiungi dipendenza:

    Nel file di build Gradle del modulo (di solito app/build.gradle), aggiungi le dipendenze di Google Play Services per play-services-base-testing nel test.

      dependencies {
        // other dependencies...
    
        testImplementation 'com.google.android.gms:play-services-base-testing:16.1.0'
      }
    
  2. Crea un modulo Hilt per fornire 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);
      }
    }
    
  3. Inserisci ModuleInstallClient nell'attività:

    Kotlin

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

    Java

    @AndroidEntryPoint
    public class MyActivity extends AppCompatActivity {
      @Inject ModuleInstallClient moduleInstallClient;
    
      ...
    }
    
  4. Sostituisci l'associazione nel test:

    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 la disponibilità dei moduli

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 risultato per una richiesta di installazione differita

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 il risultato per una richiesta di installazione 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...
}