機能モジュールを使用すると、アプリのベース モジュールから特定の機能とリソースを分離して App Bundle に組み込むことができます。たとえば、Play Feature Delivery により、ユーザーはアプリのベース APK をインストールした後で、そのようなコンポーネントをオンデマンドでダウンロードしてインストールできます。
例として、テキスト メッセージ アプリに画像メッセージを撮影して送信する機能があるにもかかわらず、その機能を使用するユーザーの割合が少ない場合を考えてみましょう。この場合、画像メッセージをダウンロード可能な機能モジュールとして組み込むのが妥当です。そうすれば、すべてのユーザーにとってアプリの初回ダウンロード サイズが小さくなり、画像メッセージを送信するユーザーの場合のみ追加コンポーネントのダウンロードが必要になります。
このタイプのモジュール化には多くの労力と、場合によってはアプリの既存のコードのリファクタリングが必要になるため、アプリのどの機能をユーザーにオンデマンドで提供すればメリットが最大になるかを慎重に検討してください。オンデマンド機能の最適な事例とガイドラインの理解を深めるには、オンデマンド配信の UX に関するおすすめの方法をご覧ください。
オンデマンド配信のような高度な配信方法を有効にせずに、時間をかけて徐々にアプリの機能をモジュール化する場合は、代わりにインストール時の配信を設定します。
このページでは、機能モジュールをアプリ プロジェクトに追加して、オンデマンド配信用に設定する方法を紹介します。はじめに、Android Studio 3.5 以上と Android Gradle Plugin 3.5.0 以上を使用していることをご確認ください。
新しいモジュールをオンデマンド配信用に設定する
新しい機能モジュールを作成する最も簡単な方法は、Android Studio 3.5 以上を使用することです。機能モジュールは本来的にベースアプリ モジュールと依存関係にあるため、既存のアプリ プロジェクトにしか追加できません。
Android Studio を使用してアプリ プロジェクトに機能モジュールを追加する手順は次のとおりです。
- IDE 内でアプリ プロジェクトをまだ開いていない場合は、開きます。
- メニューバーから [File] > [New] > [New Module] を選択します。
- [Create New Module] ダイアログで [Dynamic Feature Module] を選択して [Next] をクリックします。
- [Configure your new module] で次のように入力します。
- [Base application module] プルダウン メニューからそのアプリ プロジェクトのベース モジュールを選択します。
- [Module name] にモジュール名を指定します。この名前は IDE によって Gradle 設定ファイル内でそのモジュールを Gradle サブプロジェクトとして識別するために使用されます。サブプロジェクト名の最後の要素は、App Bundle をビルドする際、Gradle によって、
<manifest split>
属性を機能モジュールのマニフェストに挿入するために使用されます。 - [Package name] にモジュールのパッケージ名を指定します。Android Studio でのデフォルトのパッケージ名は、ベース モジュールのルート パッケージ名と、前のステップで指定したモジュール名を組み合わせた名前です。
- [Minimum API level] で、モジュールがサポートする最小 API レベルを選択します。この値はベース モジュールの値と一致している必要があります。
- [Next] をクリックします。
[Module Download Options] セクションで次のように入力します。
[Module title] にモジュールのタイトルを半角 50 文字(全角 25 文字)以内で指定します。プラットフォームがこのタイトルを使用するのは、たとえば、モジュールをダウンロードするかどうかをユーザーに確認するときに、モジュールを識別するためです。そのため、アプリのベース モジュールのタイトルを、文字列リソースとして指定して、翻訳できるようにする必要があります。Android Studio を使ってこのモジュールを作成すると、IDE はその文字列リソースをベース モジュールに追加して、次のようなエントリをその機能モジュールのマニフェストに挿入します。
<dist:module ... dist:title="@string/feature_title"> </dist:module>
[Install-time inclusion] のプルダウン メニューで、[Do not include module at install-time] を選択します。Android Studio はデベロッパーの選択に合わせて、モジュールのマニフェストに以下を挿入します。
<dist:module ... > <dist:delivery> <dist:on-demand/> </dist:delivery> </dist:module>
Android 4.4(API レベル 20)以下を搭載したデバイスでこのモジュールを使用可能にして、マルチ APK に含める場合は、[Fusing] チェックボックスをオンにします。このモジュールのオンデマンド動作を有効にし、融合(Fusing)を無効にすると、分割 APK のダウンロードとインストールに対応していないデバイスを対象外にすることができます。Android Studio はデベロッパーの選択に合わせて、モジュールのマニフェストに以下を挿入します。
<dist:module ...> <dist:fusing dist:include="true | false" /> </dist:module>
[Finish] をクリックします。
Android Studio でモジュールの作成を終えた後、[Project] ペインでモジュールの内容を確認します(メニューバーから [View] > [Tool Windows] > [Project] を選択します)。デフォルトのコード、リソース、編成は、標準のアプリ モジュールの場合と同様です。
次に、Play Feature Delivery Library を使用して、オンデマンド インストール機能を実装する必要があります。
プロジェクトに Play Feature Delivery Library を追加する
まず、プロジェクトに Play Feature Delivery Library を追加する必要があります。
オンデマンド モジュールをリクエストする
アプリで機能モジュールを使用する必要があるときは、アプリが SplitInstallManager
クラスを使用してフォアグラウンドで動作しているときにモジュールをリクエストできます。リクエストを行う際、アプリは、ターゲット モジュールのマニフェスト内の split
要素によって定義されているとおりにモジュールの名前を指定する必要があります。Android Studio で機能モジュールを作成すると、ビルドシステムは、指定されたモジュール名を使用して、コンパイル時にこのプロパティをモジュールのマニフェストに挿入します。詳しくは、機能モジュールのマニフェストをご覧ください。
たとえば、デバイスのカメラを使用して画像メッセージをキャプチャして送信するオンデマンド モジュールを備えたアプリがあり、このオンデマンド モジュールはマニフェスト内で split="pictureMessages"
を指定しているとします。SplitInstallManager
を使用して pictureMessages
モジュール(ならびにプロモーション フィルタ用の追加モジュール)をリクエストするサンプルを以下に示します。
Kotlin
// Creates an instance of SplitInstallManager. val splitInstallManager = SplitInstallManagerFactory.create(context) // Creates a request to install a module. val request = SplitInstallRequest .newBuilder() // You can download multiple on demand modules per // request by invoking the following method for each // module you want to install. .addModule("pictureMessages") .addModule("promotionalFilters") .build() splitInstallManager // Submits the request to install the module through the // asynchronous startInstall() task. Your app needs to be // in the foreground to submit the request. .startInstall(request) // You should also be able to gracefully handle // request state changes and errors. To learn more, go to // the section about how to Monitor the request state. .addOnSuccessListener { sessionId -> ... } .addOnFailureListener { exception -> ... }
Java
// Creates an instance of SplitInstallManager. SplitInstallManager splitInstallManager = SplitInstallManagerFactory.create(context); // Creates a request to install a module. SplitInstallRequest request = SplitInstallRequest .newBuilder() // You can download multiple on demand modules per // request by invoking the following method for each // module you want to install. .addModule("pictureMessages") .addModule("promotionalFilters") .build(); splitInstallManager // Submits the request to install the module through the // asynchronous startInstall() task. Your app needs to be // in the foreground to submit the request. .startInstall(request) // You should also be able to gracefully handle // request state changes and errors. To learn more, go to // the section about how to Monitor the request state. .addOnSuccessListener(sessionId -> { ... }) .addOnFailureListener(exception -> { ... });
アプリがオンデマンド モジュールをリクエストすると、Play Feature Delivery Library は「ファイア アンド フォーゲット(撃ちっぱなし)」戦略を採用します。つまり、モジュールをプラットフォームにダウンロードするように求めるリクエストの送信はしますが、インストールが成功したかどうかのモニタリングはしません。インストール後にユーザー ジャーニーを続行するため、またはエラーを適切に処理するため、必ずリクエストの状態をモニタリングしてください。
注: デバイスにすでにインストールされている機能モジュールをリクエストしても問題ありません。API は、モジュールがインストール済みであることを検出すると、直ちにリクエストは完了したと判断します。また、モジュールは、インストールされた後 Google Play によって常に自動更新されます。つまり、App Bundle の新しいバージョンをアップロードすると、そのアプリに属するすべてのインストール済み APK がプラットフォームによって更新されます。詳しくは、アプリの更新を管理するをご覧ください。
モジュールのコードとリソースにアクセスするには、アプリで SplitCompat を有効にする必要があります。ただし、Android Instant Apps で SplitCompat は不要です。
オンデマンド モジュールのインストールを遅延する
アプリがオンデマンド モジュールをすぐにダウンロードしてインストールする必要がない場合は、インストールを遅延して、アプリがバックグラウンドで動作しているときにインストールできます。たとえば、アプリの次回の起動に備えてプロモーション コンテンツをプリロードしておく場合などがこれに該当します。
後でダウンロードするモジュールを指定するには、以下のように、deferredInstall()
メソッドを呼び出します。また、SplitInstallManager.startInstall()
とは異なり、アプリがフォアグラウンドで動作していなくても遅延インストールのリクエストを開始できます。
Kotlin
// Requests an on demand module to be downloaded when the app enters // the background. You can specify more than one module at a time. splitInstallManager.deferredInstall(listOf("promotionalFilters"))
Java
// Requests an on demand module to be downloaded when the app enters // the background. You can specify more than one module at a time. splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));
遅延インストールのリクエストはベスト エフォート型のオペレーションで、進行状況をトラッキングすることはできません。したがって、遅延インストールを指定したモジュールにアクセスする前に、モジュールがインストール済みかどうかをチェックする必要があります。モジュールをすぐに利用可能にする必要がある場合は、上記のセクションで示しているように、代わりに SplitInstallManager.startInstall()
を使用してモジュールをリクエストしてください。
リクエストの状態をモニタリングする
進行状況バーを更新したり、インストール後にインテントを起動したり、リクエスト エラーを適切に処理したりするには、状態に関する最新情報を非同期 SplitInstallManager.startInstall()
タスクからリッスンする必要があります。インストール リクエストに関する最新情報を受信するには、リスナーを登録して、リクエストのセッション ID を取得します。以下をご覧ください。
Kotlin
// Initializes a variable to later track the session ID for a given request. var mySessionId = 0 // Creates a listener for request status updates. val listener = SplitInstallStateUpdatedListener { state -> if (state.sessionId() == mySessionId) { // Read the status of the request to handle the state update. } } // Registers the listener. splitInstallManager.registerListener(listener) ... splitInstallManager .startInstall(request) // When the platform accepts your request to download // an on demand module, it binds it to the following session ID. // You use this ID to track further status updates for the request. .addOnSuccessListener { sessionId -> mySessionId = sessionId } // You should also add the following listener to handle any errors // processing the request. .addOnFailureListener { exception -> // Handle request errors. } // When your app no longer requires further updates, unregister the listener. splitInstallManager.unregisterListener(listener)
Java
// Initializes a variable to later track the session ID for a given request. int mySessionId = 0; // Creates a listener for request status updates. SplitInstallStateUpdatedListener listener = state -> { if (state.sessionId() == mySessionId) { // Read the status of the request to handle the state update. } }; // Registers the listener. splitInstallManager.registerListener(listener); ... splitInstallManager .startInstall(request) // When the platform accepts your request to download // an on demand module, it binds it to the following session ID. // You use this ID to track further status updates for the request. .addOnSuccessListener(sessionId -> { mySessionId = sessionId; }) // You should also add the following listener to handle any errors // processing the request. .addOnFailureListener(exception -> { // Handle request errors. }); // When your app no longer requires further updates, unregister the listener. splitInstallManager.unregisterListener(listener);
リクエスト エラーを処理する
アプリのインストールが必ずしも成功するとは限らないように、機能モジュールのオンデマンド インストールも失敗する場合があることにご注意ください。インストールに失敗する原因としては、デバイスの空き容量が少ない、ネットワーク接続がない、ユーザーが Google Play ストアにログインしていない、などが考えられます。こうした状況をユーザー視点で適切に処理する方法については、オンデマンド配信の UX ガイドラインをご覧ください。
コードの面では、次に示すように、addOnFailureListener()
を使用してモジュールのダウンロードまたはインストールの失敗を処理する必要があります。
Kotlin
splitInstallManager .startInstall(request) .addOnFailureListener { exception -> when ((exception as SplitInstallException).errorCode) { SplitInstallErrorCode.NETWORK_ERROR -> { // Display a message that requests the user to establish a // network connection. } SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads() ... } } fun checkForActiveDownloads() { splitInstallManager // Returns a SplitInstallSessionState object for each active session as a List. .sessionStates .addOnCompleteListener { task -> if (task.isSuccessful) { // Check for active sessions. for (state in task.result) { if (state.status() == SplitInstallSessionStatus.DOWNLOADING) { // Cancel the request, or request a deferred installation. } } } } }
Java
splitInstallManager .startInstall(request) .addOnFailureListener(exception -> { switch (((SplitInstallException) exception).getErrorCode()) { case SplitInstallErrorCode.NETWORK_ERROR: // Display a message that requests the user to establish a // network connection. break; case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED: checkForActiveDownloads(); ... }); void checkForActiveDownloads() { splitInstallManager // Returns a SplitInstallSessionState object for each active session as a List. .getSessionStates() .addOnCompleteListener( task -> { if (task.isSuccessful()) { // Check for active sessions. for (SplitInstallSessionState state : task.getResult()) { if (state.status() == SplitInstallSessionStatus.DOWNLOADING) { // Cancel the request, or request a deferred installation. } } } }); }
アプリによる処理が必要となるエラー状態を以下の表に示します。
エラーコード | 説明 | 推奨されるアクション |
---|---|---|
ACTIVE_SESSIONS_LIMIT_EXCEEDED | 現在ダウンロード中の既存のリクエストが少なくとも 1 つ存在するため、このリクエストは拒否されました。 | まだダウンロード中のリクエストが存在するかどうかをチェックします。上記のサンプルをご覧ください。 |
MODULE_UNAVAILABLE | Google Play は、現在インストールされているアプリのバージョン、デバイス、ユーザーの Google Play アカウントに基づいて、リクエストされたモジュールを見つけることができませんでした。 | ユーザーがモジュールに対するアクセス権限を持っていない場合は、ユーザーに通知します。 |
INVALID_REQUEST | Google Play はリクエストを受信しましたが、リクエストが無効です。 | リクエストに含まれている情報が完全で正確であることを確認します。 |
SESSION_NOT_FOUND | 指定されたセッション ID のセッションが見つかりませんでした。 | セッション ID でリクエストの状態をモニタリングする場合は、セッション ID が正しいことを確認します。 |
API_NOT_AVAILABLE | 現在のデバイスでは、Play Feature Delivery Library がサポートされていません。そのため、このデバイスはオンデマンドで機能をダウンロードしてインストールすることができません。 | Android 4.4(API レベル 20)以前を搭載しているデバイスの場合、dist:fusing マニフェスト プロパティを使用して、インストール時に機能モジュールを組み込む必要があります。詳しくは、機能モジュールのマニフェストをご覧ください。
|
NETWORK_ERROR | ネットワーク エラーのため、リクエストが失敗しました。 | ネットワーク接続を確立するか、別のネットワークに変更することをユーザーに求めます。 |
ACCESS_DENIED | 権限が不十分なため、アプリがリクエストを登録できませんでした。 | 通常、このエラーはアプリがバックグラウンドで動作しているときに発生します。アプリがフォアグラウンドに戻ってから、リクエストを試行するようにします。 |
INCOMPATIBLE_WITH_EXISTING_SESSION | リクエスト内に、すでにリクエスト済みだがまだインストールされていないモジュールが 1 つまたは複数含まれています。 | リクエスト済みのモジュールを含まない新しいリクエストを作成するか、現在リクエスト中のすべてのモジュールのインストールが完了するのを待ってからリクエストを再試行します。 なお、インストール済みのモジュールをリクエストしてもエラーにはなりません。 |
SERVICE_DIED | リクエストの処理を担当するサービスが停止しています。 | リクエストを再試行します。
|
INSUFFICIENT_STORAGE | 機能モジュールをインストールするにはデバイスの空き容量が不足しています。 | この機能をインストールするには空き容量が不足していることをユーザーに通知します。 |
SPLITCOMPAT_VERIFICATION_ERROR、SPLITCOMPAT_EMULATION_ERROR、SPLITCOMPAT_COPY_ERROR | SplitCompat が機能モジュールを読み込めませんでした。 | これらのエラーは、次回のアプリの再起動後に自動的に解決されます。 |
PLAY_STORE_NOT_FOUND | Play ストア アプリがデバイスにインストールされていません。 | この機能をダウンロードするには Play ストア アプリが必要であることをユーザーに伝えます。 |
APP_NOT_OWNED | このアプリは Google Play によってインストールされたものではないため、この機能をダウンロードできません。このエラーは、遅延インストールの場合にのみ発生する可能性があります。 | Google Play からアプリを入手するには startInstall() を使用します。これにより、必要なユーザーの同意を得ることができます。 |
INTERNAL_ERROR | Play ストアで内部エラーが発生しました。 | リクエストを再試行します。 |
ユーザーがオンデマンド モジュールのダウンロードをリクエストしてエラーが発生した場合は、再試行(リクエストを再試行する)とキャンセル(リクエストを破棄する)の 2 つの選択肢をユーザーに提示するダイアログを表示してください。また、サポートが必要な場合に備え、Google Play ヘルプセンターがユーザーに表示される [ヘルプ] リンクも提供してください。
ステータスの更新を処理する
リスナーを登録し、リクエストのセッション ID を記録したら、StateUpdatedListener.onStateUpdate()
を使用して、状態の変更を処理します。以下をご覧ください。
Kotlin
override fun onStateUpdate(state : SplitInstallSessionState) { if (state.status() == SplitInstallSessionStatus.FAILED && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) { // Retry the request. return } if (state.sessionId() == mySessionId) { when (state.status()) { SplitInstallSessionStatus.DOWNLOADING -> { val totalBytes = state.totalBytesToDownload() val progress = state.bytesDownloaded() // Update progress bar. } SplitInstallSessionStatus.INSTALLED -> { // After a module is installed, you can start accessing its content or // fire an intent to start an activity in the installed module. // For other use cases, see access code and resources from installed modules. // If the request is an on demand module for an Android Instant App // running on Android 8.0 (API level 26) or higher, you need to // update the app context using the SplitInstallHelper API. } } } }
Java
@Override public void onStateUpdate(SplitInstallSessionState state) { if (state.status() == SplitInstallSessionStatus.FAILED && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) { // Retry the request. return; } if (state.sessionId() == mySessionId) { switch (state.status()) { case SplitInstallSessionStatus.DOWNLOADING: int totalBytes = state.totalBytesToDownload(); int progress = state.bytesDownloaded(); // Update progress bar. break; case SplitInstallSessionStatus.INSTALLED: // After a module is installed, you can start accessing its content or // fire an intent to start an activity in the installed module. // For other use cases, see access code and resources from installed modules. // If the request is an on demand module for an Android Instant App // running on Android 8.0 (API level 26) or higher, you need to // update the app context using the SplitInstallHelper API. } } }
インストール リクエストが取り得る状態を以下の表に示します。
リクエストの状態 | 説明 | 推奨されるアクション |
---|---|---|
PENDING | リクエストはユーザーにより承認済みで、まもなくダウンロードが始まります。 | 進行状況バーなどの UI コンポーネントを初期化して、ダウンロードに関するフィードバックをユーザーに提供します。 |
REQUIRES_USER_CONFIRMATION | ダウンロードにユーザーの同意が必要です。このステータスが発生する主な原因としては、アプリが Google Play からインストールされていないことが考えられます。 | Google Play から機能をダウンロードすることについてユーザーに同意を求めます。 詳しくは、ユーザーの同意を取得する方法に関するセクションをご覧ください。 |
DOWNLOADING | ダウンロード中です。 | ダウンロードの進行状況バーを提供する場合は、SplitInstallSessionState.bytesDownloaded() メソッドと SplitInstallSessionState.totalBytesToDownload() メソッドを使用して UI を更新します(この表の上にあるコードサンプルを参照)。 |
DOWNLOADED | デバイスがモジュールをダウンロードしましたが、インストールはまだ開始されていません。 | ダウンロードしたモジュールを利用可能にしてこの状態が表示されないようにするには、アプリで SplitCompat を有効にする必要があります。このアクションは、機能モジュールのコードとリソースにアクセスするために必要です。 |
INSTALLING | 現在、デバイスがモジュールをインストールしています。 | 進行状況バーを更新します。通常、この状態はすぐに終了します。 |
INSTALLED | モジュールがデバイスにインストールされました。 | モジュール内のコードとリソースにアクセスして、ユーザー ジャーニーを続行します。
Android 8.0(API レベル 26)以降で実行されている Android Instant App 用のモジュールの場合、 |
FAILED | デバイスにモジュールがインストールされる前に、リクエストが失敗しました。 | リクエストを再試行するかキャンセルすることをユーザーに求めます。 |
CANCELING | デバイスがリクエストをキャンセルしています。 | 詳しくは、インストール リクエストをキャンセルする方法に関するセクションをご覧ください。 |
CANCELED | リクエストがキャンセルされました。 |
ユーザーの同意を取得する
ダウンロード リクエストに応じる際、Google Play がユーザーの同意を求めることがあります。たとえば、アプリが Google Play によってインストールされていない場合や、モバイルデータ通信で大量にダウンロードしようとしている場合などです。そのような場合は、リクエストのステータスとして REQUIRES_USER_CONFIRMATION
が報告され、デバイスがリクエスト内のモジュールをダウンロードしてインストールするには、アプリがユーザーの同意を取得することが必要になります。ユーザーの同意を取得するには、アプリがユーザーにプロンプトを表示する必要があります。以下をご覧ください。
Kotlin
override fun onSessionStateUpdate(state: SplitInstallSessionState) { if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) { // Displays a confirmation for the user to confirm the request. splitInstallManager.startConfirmationDialogForResult( state, // an activity result launcher registered via registerForActivityResult activityResultLauncher) } ... }
Java
@Override void onSessionStateUpdate(SplitInstallSessionState state) { if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) { // Displays a confirmation for the user to confirm the request. splitInstallManager.startConfirmationDialogForResult( state, // an activity result launcher registered via registerForActivityResult activityResultLauncher); } ... }
組み込みの ActivityResultContracts.StartIntentSenderForResult
コントラクトを使用して、アクティビティ結果ランチャーを登録できます。Activity Result API をご覧ください。
ユーザーのレスポンスに応じて、リクエストのステータスが次のように更新されます。
- ユーザーが同意すると、リクエスト ステータスが
PENDING
に変わり、ダウンロードが続行されます。 - ユーザーが拒否すると、リクエスト ステータスは
CANCELED
に変わります。 - ダイアログが破棄される前にユーザーが選択を行わなかった場合、リクエストのステータスは
REQUIRES_USER_CONFIRMATION
のままになります。アプリは、ユーザーにリクエストの完了を求めるプロンプトを再度表示できます。
ユーザーのレスポンスを含むコールバックを受信するには、次のように ActivityResultCallback をオーバーライドします。
Kotlin
registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> { // Handle the user's decision. For example, if the user selects "Cancel", // you may want to disable certain functionality that depends on the module. } }
Java
registerForActivityResult( new ActivityResultContracts.StartIntentSenderForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { // Handle the user's decision. For example, if the user selects "Cancel", // you may want to disable certain functionality that depends on the module. } });
インストール リクエストをキャンセルする
リクエストしたモジュールをアプリがインストールする前にリクエストをキャンセルする必要がある場合は、リクエストのセッション ID を使用して cancelInstall()
メソッドを呼び出します。以下をご覧ください。
Kotlin
splitInstallManager // Cancels the request for the given session ID. .cancelInstall(mySessionId)
Java
splitInstallManager // Cancels the request for the given session ID. .cancelInstall(mySessionId);
モジュールにアクセスする
ダウンロードしたモジュールのコードとリソースにアクセスするには、アプリと、アプリがダウンロードした機能モジュール内の各アクティビティの両方で、SplitCompat ライブラリを有効にする必要があります。
ただし、モジュールのダウンロード後の一定期間(場合によっては数日)につき、プラットフォームにはモジュールのコンテンツへのアクセスに対して次の制限が課せられます。
- プラットフォームは、モジュールによって導入される新しいマニフェスト エントリを適用できません。
- プラットフォームは、通知など、システム UI コンポーネント用のモジュール リソースにはアクセスできません。そのようなリソースをすぐに使用する必要がある場合は、アプリのベース モジュールにリソースを組み込んでください。
SplitCompat を有効にする
ダウンロードしたモジュールのコードとリソースにアプリがアクセスするには、以下のセクションで説明している方法のいずれかを使用して SplitCompat を有効にする必要があります。
アプリに対して SplitCompat を有効にした後、アプリがアクセスできるように設定する機能モジュール内で、アクティビティごとに SplitCompat を有効にする必要もあります。
マニフェスト内で SplitCompatApplication を宣言する
SplitCompat を有効にする最も簡単な方法は、アプリのマニフェスト内で、Application
サブクラスとして SplitCompatApplication
を宣言することです。以下をご覧ください。
<application
...
android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>
アプリがデバイスにインストールされると、ダウンロードした機能モジュールのコードとリソースに自動的にアクセスできます。
実行時に SplitCompat を呼び出す
実行時に特定のアクティビティまたはサービス内で SplitCompat を有効にすることもできます。機能モジュールに含まれるアクティビティを起動するには、この方法で SplitCompat を有効にする必要があります。そのためには、attachBaseContext
をオーバーライドします。以下をご覧ください。
カスタム Application クラスを設定している場合は、代わりに SplitCompatApplication
を継承することにより、アプリに対して SplitCompat を有効にします。以下をご覧ください。
Kotlin
class MyApplication : SplitCompatApplication() { ... }
Java
public class MyApplication extends SplitCompatApplication { ... }
SplitCompatApplication
は、単純に ContextWrapper.attachBaseContext()
をオーバーライドして、SplitCompat.install(Context applicationContext)
を組み込みます。カスタム Application
クラスで SplitCompatApplication
を継承する方法を使用したくない場合は、手動で attachBaseContext()
メソッドをオーバーライドできます。以下をご覧ください。
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) // Emulates installation of future on demand modules using SplitCompat. SplitCompat.install(this) }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // Emulates installation of future on demand modules using SplitCompat. SplitCompat.install(this); }
オンデマンド モジュールが Instant App とインストール済みアプリの両方に対応している場合は、SplitCompat を条件付きで呼び出すことができます。以下をご覧ください。
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this); } }
モジュール アクティビティに対して SplitCompat を有効にする
ベースアプリに対して SplitCompat を有効にした後、機能モジュールでアプリがダウンロードするアクティビティごとに SplitCompat を有効にする必要があります。そのためには、SplitCompat.installActivity()
メソッドを呼び出します。以下をご覧ください。
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) // Emulates installation of on demand modules using SplitCompat. SplitCompat.installActivity(this) }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // Emulates installation of on demand modules using SplitCompat. SplitCompat.installActivity(this); }
機能モジュールで定義されたコンポーネントにアクセスする
機能モジュールで定義されたアクティビティを開始する
SplitCompat を有効にした後、startActivity()
を使用して、機能モジュールで定義されたアクティビティを開始できます。
Kotlin
startActivity(Intent() .setClassName("com.package", "com.package.module.MyActivity") .setFlags(...))
Java
startActivity(new Intent() .setClassName("com.package", "com.package.module.MyActivity") .setFlags(...));
setClassName
の最初のパラメータはアプリのパッケージ名であり、2 番目のパラメータはアクティビティの完全なクラス名です。
オンデマンドでダウンロードした機能モジュールにアクティビティがある場合、アクティビティで SplitCompat を有効にする必要があります。
機能モジュールで定義されたサービスを開始する
SplitCompat を有効にした後、startService()
を使用して、機能モジュールで定義されたサービスを開始できます。
Kotlin
startService(Intent() .setClassName("com.package", "com.package.module.MyService") .setFlags(...))
Java
startService(new Intent() .setClassName("com.package", "com.package.module.MyService") .setFlags(...));
機能モジュールで定義されたコンポーネントをエクスポートする
エクスポートされた Android コンポーネントは、オプションのモジュール内に含めないでください。
ビルドシステムは、すべてのモジュールのマニフェスト エントリをベース モジュールにマージします。エクスポートしたコンポーネントがオプション モジュールに含まれる場合は、そのモジュールがインストールされる前でもアクセスでき、別のアプリから呼び出されたときにコードが見つからないためにクラッシュする可能性があります。
内部コンポーネントの場合は問題ありません。内部コンポーネントにはアプリからしかアクセスできず、アプリはコンポーネントにアクセスする前にモジュールがインストールされていることを確認できるためです。
エクスポートされたコンポーネントが必要で、そのコンテンツをオプション モジュールに入れる場合は、プロキシ パターンの実装をご検討ください。これを行うには、プロキシでエクスポートされたコンポーネントをベースに追加します。アクセスされると、プロキシ コンポーネントはコンテンツを含むモジュールの存在を確認できます。モジュールが存在する場合、プロキシ コンポーネントは Intent
を介してモジュールから内部コンポーネントを開始し、呼び出し元のアプリからインテントをリレーできます。モジュールが存在しない場合、コンポーネントはモジュールをダウンロードするか、呼び出し元アプリに該当するエラー メッセージを返すことができます。
インストールしたモジュールのコードとリソースにアクセスする
ベースアプリのコンテキストと機能モジュール内のアクティビティに対して SplitCompat を有効にした場合、オプション モジュールがインストールされると、機能モジュールのコードとリソースをベース APK の一部であるかのように使用できます。
別のモジュールからコードにアクセスする
モジュールからベースコードにアクセスする
ベース モジュール内のコードは、他のモジュールで直接使用できます。特別な対応は必要ありません。必要なクラスをインポートして使用するだけです。
別のモジュールからモジュール コードにアクセスする
モジュール内のオブジェクトまたはクラスに、別のモジュールから直接静的にアクセスすることはできませんが、リフレクションを使用して間接的にアクセスすることはできます。
ただし、リフレクションによるパフォーマンス低下を考慮して、アクセスの頻度に注意する必要があります。複雑なユースケースでは、Dagger 2 のような依存関係挿入フレームワークを使用して、アプリのライフタイムごとに 1 つのリフレクション呼び出しを保証します。
インスタンス化後のオブジェクトとのインタラクションを簡素化するために、ベース モジュールでインターフェースを定義し、その実装を機能モジュールで定義することをおすすめします。たとえば、次のようになります。
Kotlin
// In the base module interface MyInterface { fun hello(): String } // In the feature module object MyInterfaceImpl : MyInterface { override fun hello() = "Hello" } // In the base module, where we want to access the feature module code val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl") .kotlin.objectInstance as MyInterface).hello();
Java
// In the base module public interface MyInterface { String hello(); } // In the feature module public class MyInterfaceImpl implements MyInterface { @Override public String hello() { return "Hello"; } } // In the base module, where we want to access the feature module code String stringFromModule = ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();
別のモジュールからリソースとアセットにアクセスする
モジュールをインストールすると、モジュール内のリソースとアセットに標準的な方法でアクセスできます。ただし、次の 2 点に注意してください。
- 別のモジュールからリソースにアクセスする場合、リソースには名前でアクセスできますが、モジュールはリソース ID にアクセスできません。リソースの参照に使用するパッケージは、リソースが定義されているモジュールのパッケージです。
- 新しくインストールしたモジュール内に存在するアセットまたはリソースにアプリの別のインストール済みモジュールからアクセスする場合は、アプリ コンテキストを使用してアクセスする必要があります。リソースにアクセスしようとするコンポーネントのコンテキストは、依然として更新されません。代わりに、機能モジュールのインストール後、対象コンポーネントの再作成(Activity.recreate() の呼び出しなど)または SplitCompat の再インストールを行う方法もあります。
オンデマンド配信を使用してアプリにネイティブ コードを読み込む
機能モジュールのオンデマンド配信を使用する場合は、ReLinker を使用してすべてのネイティブ ライブラリを読み込むことをおすすめします。ReLinker は、機能モジュールのインストール後にネイティブ ライブラリを読み込む際の問題を解決します。ReLinker の詳細については Android JNI のヒントをご覧ください。
オプション モジュールからネイティブ コードを読み込む
スプリットをインストールしたら、ReLinker でネイティブ コードを読み込むことをおすすめします。Instant App の場合は、この特別なメソッドを使用してください。
System.loadLibrary()
を使用してネイティブ コードを読み込み、ネイティブ ライブラリにモジュール内の別のライブラリとの依存関係がある場合は、最初にその別のライブラリを手動で読み込む必要があります。ReLinker を使用している場合、同様のオペレーションは Relinker.recursively().loadLibrary()
です。
ネイティブ コードで dlopen()
を使用して、オプション モジュールで定義されたライブラリを読み込む場合、相対ライブラリパスでは機能しません。ClassLoader.findLibrary()
を介して Java コードからライブラリの絶対パスを取得して、dlopen()
呼び出しで使用することをおすすめします。ネイティブ コードを入力する前にこれを行うか、ネイティブ コードから Java への JNI 呼び出しを使用します。
インストール済みの Android Instant Apps にアクセスする
Android Instant App モジュールが INSTALLED
として報告されたら、更新版のアプリ コンテキストを使用して、モジュールのコードとリソースにアクセスできます。モジュールをインストールする前にアプリが作成したコンテキスト(たとえば、すでに変数内に格納されているコンテキスト)には、新しいモジュールのコンテンツは含まれていません。新しいモジュールのコンテンツを含む更新版コンテキストを取得するには、たとえば、createPackageContext
を使用します。
Kotlin
// Generate a new context as soon as a request for a new module // reports as INSTALLED. override fun onStateUpdate(state: SplitInstallSessionState ) { if (state.sessionId() == mySessionId) { when (state.status()) { ... SplitInstallSessionStatus.INSTALLED -> { val newContext = context.createPackageContext(context.packageName, 0) // If you use AssetManager to access your app’s raw asset files, you’ll need // to generate a new AssetManager instance from the updated context. val am = newContext.assets } } } }
Java
// Generate a new context as soon as a request for a new module // reports as INSTALLED. @Override public void onStateUpdate(SplitInstallSessionState state) { if (state.sessionId() == mySessionId) { switch (state.status()) { ... case SplitInstallSessionStatus.INSTALLED: Context newContext = context.createPackageContext(context.getPackageName(), 0); // If you use AssetManager to access your app’s raw asset files, you’ll need // to generate a new AssetManager instance from the updated context. AssetManager am = newContext.getAssets(); } } }
Android 8.0 以上の Android Instant Apps
Android 8.0(API レベル 26)以降の Android Instant App のオンデマンド モジュールをリクエストする場合は、インストール リクエストが INSTALLED
として報告された後、SplitInstallHelper.updateAppInfo(Context context)
呼び出しを通じて、新しいモジュールのコンテキストでアプリを更新する必要があります。そうしない限り、アプリはモジュールのコードとリソースを認識しません。アプリのメタデータを更新したら、新しい Handler
を呼び出して、次回のメインスレッド イベント中にモジュールのコンテンツを読み込む必要があります。以下をご覧ください。
Kotlin
override fun onStateUpdate(state: SplitInstallSessionState ) { if (state.sessionId() == mySessionId) { when (state.status()) { ... SplitInstallSessionStatus.INSTALLED -> { // You need to perform the following only for Android Instant Apps // running on Android 8.0 (API level 26) and higher. if (BuildCompat.isAtLeastO()) { // Updates the app’s context with the code and resources of the // installed module. SplitInstallHelper.updateAppInfo(context) Handler().post { // Loads contents from the module using AssetManager val am = context.assets ... } } } } } }
Java
@Override public void onStateUpdate(SplitInstallSessionState state) { if (state.sessionId() == mySessionId) { switch (state.status()) { ... case SplitInstallSessionStatus.INSTALLED: // You need to perform the following only for Android Instant Apps // running on Android 8.0 (API level 26) and higher. if (BuildCompat.isAtLeastO()) { // Updates the app’s context with the code and resources of the // installed module. SplitInstallHelper.updateAppInfo(context); new Handler().post(new Runnable() { @Override public void run() { // Loads contents from the module using AssetManager AssetManager am = context.getAssets(); ... } }); } } } }
C / C++ ライブラリを読み込む
デバイスが Instant App でダウンロードしたモジュールから C / C++ ライブラリを読み込む場合は、SplitInstallHelper.loadLibrary(Context context, String libName)
を使用します。以下をご覧ください。
Kotlin
override fun onStateUpdate(state: SplitInstallSessionState) { if (state.sessionId() == mySessionId) { when (state.status()) { SplitInstallSessionStatus.INSTALLED -> { // Updates the app’s context as soon as a module is installed. val newContext = context.createPackageContext(context.packageName, 0) // To load C/C++ libraries from an installed module, use the following API // instead of System.load(). SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”) ... } } } }
Java
public void onStateUpdate(SplitInstallSessionState state) { if (state.sessionId() == mySessionId) { switch (state.status()) { case SplitInstallSessionStatus.INSTALLED: // Updates the app’s context as soon as a module is installed. Context newContext = context.createPackageContext(context.getPackageName(), 0); // To load C/C++ libraries from an installed module, use the following API // instead of System.load(). SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”); ... } } }
既知の制限事項
- オプション モジュールからリソースまたはアセットにアクセスするアクティビティでは、Android WebView を使用できません。これは、Android API レベル 28 以下では WebView と SplitCompat の間に互換性がないためです。
- Android
ApplicationInfo
オブジェクト、そのコンテンツ、またはそれらを含むオブジェクトをアプリ内でキャッシュに保存することはできません。こうしたオブジェクトは、必要に応じて常にアプリ コンテキストから取得する必要があります。このようなオブジェクトをキャッシュに保存すると、機能モジュールのインストール時にアプリがクラッシュする可能性があります。
インストールしたモジュールを管理する
デバイスに現在インストールされている機能モジュールを確認するには、SplitInstallManager.getInstalledModules()
を呼び出します。これにより、インストール済みモジュールの名前の Set<String>
が返されます。以下をご覧ください。
Kotlin
val installedModules: Set<String> = splitInstallManager.installedModules
Java
Set<String> installedModules = splitInstallManager.getInstalledModules();
モジュールをアンインストールする
モジュールのアンインストールをデバイスにリクエストするには、SplitInstallManager.deferredUninstall(List<String> moduleNames)
を呼び出します。以下をご覧ください。
Kotlin
// Specifies two feature modules for deferred uninstall. splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))
Java
// Specifies two feature modules for deferred uninstall. splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));
モジュールのアンインストールは、すぐには実行されません。デバイスは、保存容量を節約するため、必要に応じてバックグラウンドでモジュールをアンインストールします。上記のセクションで説明しているように、デバイスがモジュールを削除したかどうかを確認するには、SplitInstallManager.getInstalledModules()
を呼び出してその結果を調べます。
追加の言語リソースをダウンロードする
App Bundle の場合、デバイスはアプリの実行に必要なコードとリソースのみをダウンロードします。したがって、言語リソースについては、ユーザーのデバイスは、デバイスの設定で現在選択されている 1 つ以上の言語と一致するアプリの言語リソースのみをダウンロードします。
アプリで追加の言語リソースを利用できるようにする場合(アプリ内言語選択ツールを実装する場合など)は、Play Feature Delivery Library を使用することで、オンデマンドでダウンロードできます。プロセスは、機能モジュールをダウンロードする場合と同様です。以下をご覧ください。
Kotlin
// Captures the user’s preferred language and persists it // through the app’s SharedPreferences. sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply() ... // Creates a request to download and install additional language resources. val request = SplitInstallRequest.newBuilder() // Uses the addLanguage() method to include French language resources in the request. // Note that country codes are ignored. That is, if your app // includes resources for “fr-FR” and “fr-CA”, resources for both // country codes are downloaded when requesting resources for "fr". .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION))) .build() // Submits the request to install the additional language resources. splitInstallManager.startInstall(request)
Java
// Captures the user’s preferred language and persists it // through the app’s SharedPreferences. sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply(); ... // Creates a request to download and install additional language resources. SplitInstallRequest request = SplitInstallRequest.newBuilder() // Uses the addLanguage() method to include French language resources in the request. // Note that country codes are ignored. That is, if your app // includes resources for “fr-FR” and “fr-CA”, resources for both // country codes are downloaded when requesting resources for "fr". .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION))) .build(); // Submits the request to install the additional language resources. splitInstallManager.startInstall(request);
このリクエストは、機能モジュールに対するリクエストと同じように処理されます。したがって、通常の場合と同様にリクエストの状態をモニタリングできます。
アプリが追加の言語リソースをすぐに必要としない場合は、インストールを遅延して、アプリがバックグラウンドで動作しているときにインストールできます。下記の例をご覧ください。
Kotlin
splitInstallManager.deferredLanguageInstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
Java
splitInstallManager.deferredLanguageInstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
ダウンロードした言語リソースにアクセスする
ダウンロードした言語リソースにアクセスするには、それらのリソースへのアクセスを必要とする各アクティビティの attachBaseContext()
メソッド内で SplitCompat.installActivity()
メソッドを実行する必要があります。以下をご覧ください。
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) SplitCompat.installActivity(this) }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); SplitCompat.installActivity(this); }
アプリがダウンロードした言語リソースを使用するアクティビティごとに、ベース コンテキストを更新し、Configuration
を通じて新しい言語 / 地域を設定します。
Kotlin
override fun attachBaseContext(base: Context) { val configuration = Configuration() configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION))) val context = base.createConfigurationContext(configuration) super.attachBaseContext(context) SplitCompat.install(this) }
Java
@Override protected void attachBaseContext(Context base) { Configuration configuration = new Configuration(); configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION))); Context context = base.createConfigurationContext(configuration); super.attachBaseContext(context); SplitCompat.install(this); }
変更を有効にするには、新しい言語をインストールして使用する準備が整った後に、アクティビティを再作成する必要があります。Activity#recreate()
メソッドを使用できます。
Kotlin
when (state.status()) { SplitInstallSessionStatus.INSTALLED -> { // Recreates the activity to load resources for the new language // preference. activity.recreate() } ... }
Java
switch (state.status()) { case SplitInstallSessionStatus.INSTALLED: // Recreates the activity to load resources for the new language // preference. activity.recreate(); ... }
追加の言語リソースをアンインストールする
機能モジュールと同様、追加リソースはいつでもアンインストールできます。アンインストールをリクエストする前に、まず、現在インストールされている言語を確認しておくことをおすすめします。以下をご覧ください。
Kotlin
val installedLanguages: Set<String> = splitInstallManager.installedLanguages
Java
Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();
次に、deferredLanguageUninstall()
メソッドを使用して、アンインストールする言語を決定します。以下をご覧ください。
Kotlin
splitInstallManager.deferredLanguageUninstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
Java
splitInstallManager.deferredLanguageUninstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
モジュール インストールをローカルでテストする
Play Feature Delivery Library を使用すると、Play ストアに接続せずに、アプリの以下の機能をローカルでテストできます。
- モジュールのインストールをリクエストして監視する。
- インストール エラーを処理する。
SplitCompat
を使用してモジュールにアクセスする。
このページでは、アプリの分割 APK をテストデバイスにデプロイし、Play Feature Delivery でその APK を自動的に使用して、Google Play ストアとの間でのモジュールのリクエスト、ダウンロード、インストールをシミュレートする方法について説明します。
アプリのロジックを変更する必要はありませんが、以下の要件を満たす必要があります。
bundletool
の最新版をダウンロードしてインストールします。App Bundle からインストール可能な APK の新しいセットをビルドするために、bundletool
が必要です。
APK セットをビルドする
まだビルドしていない場合は、次の手順で、アプリの分割 APK をビルドします。
- 以下のいずれかの方法でアプリの App Bundle を作成します。
- Android Studio と Android Plugin for Gradle を使用し、Android App Bundle をビルドして署名します。
- コマンドラインから App Bundle をビルドします。
bundletool
を使用して、次のコマンドですべてのデバイス設定向けの APK セットを生成します。bundletool build-apks --local-testing --bundle my_app.aab --output my_app.apks
--local-testing
フラグを使用すると、Google Play ストアに接続せずに Play Feature Delivery Library でローカル分割 APK を使用して機能モジュールのインストールをテストできるようになるメタデータが APK のマニフェストに指定されます。
アプリをデバイスにデプロイする
--local-testing
フラグを使用して APK セットをビルドした後、bundletool
を使用して、アプリのベース バージョンをインストールし、その他の APK をデバイスのローカル ストレージに転送します。次のコマンドで両方の操作を実施できます。
bundletool install-apks --apks my_app.apks
これで、アプリが起動し、機能モジュールのダウンロードとインストールのユーザーフローが完了すると、bundletool
によってデバイスのローカル ストレージに転送された APK を Play Feature Delivery Library が使用するようになります。
ネットワーク エラーをシミュレートする
Play ストアからのモジュールのインストールをシミュレートするために、Play Feature Delivery Library は SplitInstallManager
の代わりとなる FakeSplitInstallManager
を使用してモジュールをリクエストします。bundletool
を --local-testing
フラグとともに使用して、APK セットをビルドし、テストデバイスにデプロイすると、アプリの API 呼び出しを自動的に切り替えて SplitInstallManager
の代わりに FakeSplitInstallManager
を呼び出すよう Play Feature Delivery Library に指示するメタデータが提供されます。
FakeSplitInstallManager
に含まれるブール値フラグにより、次回アプリがモジュールのインストールをリクエストしたときに、ネットワーク エラーをシミュレートできるようになります。テストで FakeSplitInstallManager
にアクセスするために、下記のように、FakeSplitInstallManagerFactory
を使用してインスタンスを取得します。
Kotlin
// Creates an instance of FakeSplitInstallManager with the app's context. val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context) // Tells Play Feature Delivery Library to force the next module request to // result in a network error. fakeSplitInstallManager.setShouldNetworkError(true)
Java
// Creates an instance of FakeSplitInstallManager with the app's context. FakeSplitInstallManager fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context); // Tells Play Feature Delivery Library to force the next module request to // result in a network error. fakeSplitInstallManager.setShouldNetworkError(true);