如Google Play 服务概览一文所述,由 Google Play 服务提供支持的 SDK 由 Google 认证的 Android 设备上的设备端服务提供支持。为了节省整个设备阵容中的存储空间和内存,部分服务以模块的形式提供,这些模块会在您的应用需要相关功能时按需安装。例如,在 Google Play 服务中使用模型时,机器学习套件会提供此选项。
在大多数情况下,当您的应用使用需要这些模块的 API 时,Google Play 服务 SDK 会自动下载并安装必要的模块。不过,您可能希望更好地控制该过程,例如,您希望通过提前安装模块来改善用户体验。
借助 ModuleInstallClient
API,您可以:
- 检查模块是否已安装到设备上。
- 请求安装模块。
- 监控安装进度。
- 处理安装过程中的错误。
本指南介绍了如何使用 ModuleInstallClient
管理应用中的模块。请注意,以下代码段使用 TensorFlow Lite SDK (play-services-tflite-java
) 作为示例,但这些步骤适用于与 OptionalModuleApi
集成的任何库。
准备工作
为了让您的应用做好准备,请完成以下部分中的步骤。
应用要满足的前提条件
确保您应用的 build 文件使用以下值:
minSdkVersion
为23
或更高版本
配置您的应用
在顶级
settings.gradle
文件的dependencyResolutionManagement
代码块中,添加 Google 的 Maven 制品库和 Maven 中央制品库:dependencyResolutionManagement { repositories { google() mavenCentral() } }
在模块的 Gradle build 文件(通常为
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)
Java
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... }
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… });
请求延迟安装
如果您不需要立即安装该模块,可以请求延迟安装。这样,Google Play 服务便可以在后台安装模块,可能在设备处于空闲状态且连接到 Wi-Fi 时执行此操作。
获取
ModuleInstallClient
的实例:Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
Java
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
发送延迟请求:
Kotlin
val optionalModuleApi = TfLite.getClient(context) moduleInstallClient.deferredInstall(optionalModuleApi)
Java
OptionalModuleApi optionalModuleApi = TfLite.getClient(context); moduleInstallClient.deferredInstall(optionalModuleApi);
请求紧急安装模块
如果您的应用需要立即使用该模块,可以申请紧急安装。这将尝试尽快安装模块,即使这意味着使用移动流量也是如此。
获取
ModuleInstallClient
的实例:Kotlin
val moduleInstallClient = ModuleInstall.getClient(context)
Java
ModuleInstallClient moduleInstallClient = ModuleInstall.getClient(context);
(可选)创建
InstallStatusListener
以监控安装进度。如果您想在应用的界面中显示下载进度(例如,使用进度条),可以创建
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()
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();
配置
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()
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();
发送安装请求:
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... });
使用 FakeModuleInstallClient
测试应用
Google Play 服务 SDK 提供了 FakeModuleInstallClient
,以便您使用依赖项注入在测试中模拟模块安装 API 的结果。这有助于您在不同场景中测试应用的行为,而无需将其部署到真实设备。
应用要满足的前提条件
将应用配置为使用 Hilt 依赖项注入框架。
在测试中将 ModuleInstallClient
替换为 FakeModuleInstallClient
如需在测试中使用 FakeModuleInstallClient
,您需要将 ModuleInstallClient
绑定替换为虚构实现。
添加依赖项:
在模块的 Gradle build 文件(通常为
app/build.gradle
)中,在测试中添加play-services-base-testing
的 Google Play 服务依赖项。dependencies { // other dependencies... testImplementation 'com.google.android.gms:play-services-base-testing:16.1.0' }
创建一个 Hilt 模块来提供
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); } }
在 activity 中注入
ModuleInstallClient
:Kotlin
@AndroidEntryPoint class MyActivity: AppCompatActivity() { @Inject lateinit var moduleInstallClient: ModuleInstallClient ... }
Java
@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 ... }
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; ... }
模拟不同的场景
借助 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... }
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... }
模拟延迟安装请求的结果
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... }
模拟紧急安装请求的结果
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... }