Как описано в статье «Обзор сервисов Google Play» , SDK на базе сервисов Google Play поддерживаются встроенными службами на устройствах Android, сертифицированных Google. Чтобы сэкономить место и память для всего парка устройств, некоторые службы устанавливаются по требованию, когда ясно, что конкретному устройству требуются соответствующие функции. Например, ML Kit предоставляет такую возможность при использовании моделей в сервисах Google Play.
Наиболее распространенным случаем является загрузка и установка службы (или «модуля») параллельно с приложением, которому она требуется, на основе зависимости в AndroidManifest.xml
SDK. Для большего контроля API установки модуля предоставляют возможность явно проверять доступность модуля, запрашивать установку модуля, отслеживать состояние запроса и обрабатывать ошибки.
Выполните следующие действия, чтобы обеспечить доступность API с помощью ModuleInstallClient
. Обратите внимание, что в приведенных ниже фрагментах кода в качестве примера библиотеки используется TensorFlow Lite SDK ( play-services-tflite-java
), но эти шаги применимы для любой библиотеки, интегрированной OptionalModuleApi
. Это руководство будет обновляться дополнительной информацией по мере добавления поддержки в новые SDK.
Прежде чем начать
Чтобы подготовить приложение, выполните действия, описанные в следующих разделах.
Предварительные требования приложения
Убедитесь, что в файле сборки вашего приложения используются следующие значения:
-
minSdkVersion
19
или выше.
Настройте свое приложение
В файле
settings.gradle
верхнего уровня включите репозиторий Google Maven и центральный репозиторий Maven в блокdependencyResolutionManagement
:dependencyResolutionManagement { repositories { google() mavenCentral() } }
В файл сборки Gradle вашего модуля (обычно
app/build.gradle
) добавьте зависимости сервисов Google Play дляplay-services-base
и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.4.0' }
Проверить доступность модуля
Получите экземпляр
ModuleInstallClient
:Котлин
val moduleInstallClient = ModuleInstall.getClient(context)
Ява
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
Проверьте доступность дополнительного модуля с помощью
OptionalModuleApi
:Котлин
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… });
Отправьте запрос на отложенную установку
Получите экземпляр
ModuleInstallClient
:Котлин
val moduleInstallClient = ModuleInstall.getClient(context)
Ява
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
Отправьте отложенный запрос:
Котлин
val optionalModuleApi = TfLite.getClient(context) moduleInstallClient.deferredInstall(optionalModuleApi)
Ява
OptionalModuleApi optionalModuleApi = TfLite.getClient(context); moduleInstallClient.deferredInstall(optionalModuleApi);
Отправьте срочный запрос на установку модуля
Получите экземпляр
ModuleInstallClient
:Котлин
val moduleInstallClient = ModuleInstall.getClient(context)
Ява
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
(Необязательно) Создайте
InstallStatusListener
для обработки обновлений статуса установки.Если вы хотите отслеживать ход загрузки с помощью настраиваемого пользовательского интерфейса (например, индикатора выполнения), вы можете создать
InstallStatusListener
для получения обновлений состояния установки.Котлин
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
:Котлин
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()
Ява
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();
Отправьте запрос на установку:
Котлин
moduleInstallClient .installModules(moduleInstallRequest) .addOnSuccessListener { if (it.areModulesAlreadyInstalled()) { // Modules are already installed when the request is sent. } } .addOnFailureListener { // Handle failure… }
Ява
moduleInstallClient.installModules(moduleInstallRequest) .addOnSuccessListener( response -> { if (response.areModulesAlreadyInstalled()) { // Modules are already installed when the request is sent. } }) .addOnFailureListener( e -> { // Handle failure... });
Локальное тестирование с помощью FakeModuleInstallClient
SDK сервисов Google Play предоставляют FakeModuleInstallClient
, позволяющий моделировать результаты API установки модуля в тестах с использованием внедрения зависимостей.
Предварительные требования приложения
Настройте свое приложение для использования платформы внедрения зависимостей Hilt .
Замените ModuleInstallClient
на FakeModuleInstallClient
в тесте.
Добавьте зависимость:
В файл сборки Gradle вашего модуля (обычно
app/build.gradle
) добавьте зависимости сервисов Google Play дляplay-services-base-testing
в вашем тесте.dependencies { // other dependencies... testImplementation 'com.google.android.gms:play-services-base-testing:16.1.0' }
Создайте модуль Hilt для предоставления
ModuleInstallClient
:Котлин
@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
в действие:Котлин
@AndroidEntryPoint class MyActivity: AppCompatActivity() { @Inject lateinit var moduleInstallClient: ModuleInstallClient ... }
Ява
@AndroidEntryPoint public class MyActivity extends AppCompatActivity { @Inject ModuleInstallClient moduleInstallClient; ... }
Замените привязку в тесте:
Котлин
@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; ... }
Имитировать доступность модуля
Котлин
@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... }
Имитация результата запроса на отложенную установку
Котлин
@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... }
Имитация результата срочного запроса на установку
Котлин
@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... }
Ява
@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... }