如「Google Play 服務簡介」一文所述,Google Play 服務支援的 SDK 會在 Google 認證 Android 裝置上,透過裝置端服務提供支援。為在整個裝置陣容中節省儲存空間和記憶體,部分服務會以模組形式提供,並在應用程式需要相關功能時隨選安裝。舉例來說,在 Google Play 服務中使用模型時,機器學習套件會提供這個選項。
在大多數情況下,當應用程式使用需要這些模組的 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)
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
來監控安裝進度。如果您想在應用程式的 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()
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 版本檔案 (通常為
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); } }
在活動中插入
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... }