Google I/O 2018で発表された新機能であるAndroid App BundleとDynamic feature modulesが大規模アプリのペインポイントを解決しそうなので、早速利用/サンプルプロジェクトを触ってみた所感などを書きます。
Android App Bundle
Android App Bundleはリソースやネイティブライブラリを分離し、PlayStore側で端末ごとに適切なリソースを含んだapkを提供できる機能です。 具体的には
- Base APK
基本的な機能を含んだapk - Configuration APKs
言語リソースや画面濃度、ネイティブライブラリ、CPU言語を含むapk - Dynamic feature APKs
後述するDynamic feature modulesを含んだapk
の3つの種類のAPKがAndroid App Bundle(aabファイル)には含まれます。今まで個別のapkを自前で作成し配信していた作業が、全てPlayStore側でよしなにやってくれるわけです。またこの機能は4系など古いOSバージョンも対応しています。(ただし4.4以下は自動的に端末に最も適切なapkを生成して配信します)
現在はAndroid StudioのCanary14でのみ実装できますが、PlayStoreでの配信は対応しています。
大きなメリットとしては、ユーザーの端末によりダウンロード量を削減出来るようになることでしょう。aabによって生成されるapkファイルは今回公開されたbundletoolで簡単に確認できます。
ここにあるjarファイルをダウンロードし、手元のNDKを利用している会社のプロジェクトで生成したaabに実行してみた結果が以下です。
言語リソースなどの大量のapkが生成されていることがわかります。CPU言語がx86とarmeabiで4MBほどの差がありました。この他言語リソースファイルや画面濃度も踏まえると、ダウンロードするapkサイズはざっくり見積もって5 - 10MBは削減できるようでした。
なお、このbundleToolはapkの生成にも利用できるため、これをレポジトリに含んでCIでビルド、Deploygateなどの配信サービスでデプロイといったことも可能そうです。
bundletool build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks //apkファイルのbuild
詳細は以下をご覧ください。
Test Android App Bundles with bundletool | Android Developers
Android App Bundleの導入
aabファイルの生成はAndroid Studioまたはgradleコマンドで実行できます。
./gradlew bundleDebug
なお、生成されたaabファイルの中身を覗いてみると、全てProtocol Bufferでバイナリ化されていました。
あとはこのファイルを通常のapkと同じようにPlayStoreにアップロードすれば、最適なapkが配信されるようになります。ユーザーが本当にapkを受け取れるのかを確認する方法としては、今回導入されたinternal test にまずはデプロイすることが推奨されています。internal testはalphaの更に前のテストに利用できるテスター機能です。デプロイしてから1分ほどの短い時間でテストユーザーに配信されるのが特徴です。2018/5/8現在すでにPlayStoreで設定可能になっています。
実際にGoogle IOにてデモを見たところ、一瞬で配信されていました。ただしPlayStoreなので、testにデプロイする度にbuild versionをアップデートする必要がある点は気をつけたいです。
2018/5/8時点ではApp Bundleを実装するためにはIDEがAndroid Studio Canary 14が必要です。手元のプロジェクトで試した際はdrawable vectorのxmlからcolor.xmlを参照できなくなっていたため、ソースコードの修正が若干必要でした。
Dynamic feature modules
App Bundleもかなり嬉しい機能なのですが、Dynamic feature modulesはさらに利用価値が高そうです。
Dynamic feature modulesは、ある機能とリソースをベースのモジュールと分離し、必要な時にユーザーがダウンロードして利用できる機能です。Dynamic feature modulesを普段使わない機能に適用すれば、アプリサイズを劇的に減らすことが可能だと考えます。
例えば取引系のサービスではKYC(Know Your Client)という、本人確認のための機能があります。確認は条件を満たした一部のユーザーしか利用しないのですが、免許証のスキャンなどさまざまな機能が必要とされ、結果としてアプリサイズにかなりの影響(場合によっては10MB近く)を及ぼします。
このような機能をDynamic Deliveryで必要なユーザーにのみダウンロードさせるようにすれば、大幅なサイズ削減が期待できます。ただしこちらはOS 5.0(21)以上でしか利用できない点に注意してください。
Dynamic feature modulesの導入
Dynamic feature modulesの設定はほぼAndroid Libraryを作るステップと同じです。異なる点としては、Dynamic feature modules内のAndroidManifest.xmlに設定をする必要があることです。
//AndroidManifest.xml <dist:module dist:onDemand="true" // Dynamic Deliveryに対応するか dist:title="@string/name_of_module" // Module DL時に表示されるタイトル。baseのstrings.xmlに定義が必要 <dist:fusing include="true" /> // 4.4以下のapkにこのモジュールを含むか否か </dist:module>
また com.android.library
の代わりにdynamic-featureを追加します。
//bundle.gradle apply plugin: 'com.android.dynamic-feature' dependencies { implementation project(':app') }
またbase module側にはDynamic feature moduleの依存を追加します。
//app/bundle.gradle android { dynamicFeatures = [":name_of_module"] }
あるいはAndroid Studioから直接追加も出来ます。
Edit -> New modulesで "Dynamic Feature Module" を作る
On demandの設定を追加
Dynamic feature moduleの設定されたモジュールをダウンロードするにはPlay Core Libraryが必要でした。 Play Core Libraryはダウンロード中の処理や失敗時の処理などをCallbackで定義出来るほか、moduleがダウンロードされているかなどの状態を管理するライブラリです。
private val listener = SplitInstallStateUpdatedListener { state -> state.moduleNames().forEach { name -> when (state.status()) { SplitInstallSessionStatus.DOWNLOADING -> { displayLoadingState(state, "Downloading $name") } ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) manager = SplitInstallManagerFactory.create(this) } override fun onResume() { manager.registerListener(listener) super.onResume() }
実際のサンプルアプリはこちらにあります。
さて、この機能の紹介時にいくつかの疑問点があったため、Google/IOのラボ(Sandbox)で確認してきました。
Debug時のDynamic feature moduleの挙動は?
assembleXXXあるいはAndroid StudioのRunがいままでどおり利用できます。 実際にsample projectをemulatorで動かしてみたところ、moduleは一緒にapkに含まれ、以下のようなmoduleがすでに存在しているか確認できるコードにおいて必ずtrueを返すようになっていました。
private fun loadAndLaunchModule(name: String) { updateProgressMessage("Loading module $name") // モジュールがある場合はスキップする if (manager.installedModules.contains(name)) { // updateProgressMessage("Already installed") onSuccessfulLoad(name, launch = true) return }
プロダクションでのテストは?
Googlerいわく、実際のダウンロード処理の挙動を確認したい場合はPlayStoreのinternal testを利用する必要があります。
まとめ: アプリサイズの削減のためにモジュール化を意識した設計は必須
長期間アプリを開発/運用していると、さまざまな機能追加などによりアプリサイズは肥大化していきます。 実際に転送量は莫大なものとなっているようで、Android App Bundle/Dynamic feature modulesはGoogleの本気がうかがえます。
アプリサイズを小さくすることはユーザーにとっても望ましいものです。縦軸が不明なんですがアプリサイズが増加するとリニアにPlayStoreでのDL率が下がるそうな。
Dynamic feature modulesを最大限に利用するために、依存性/モジュール化を意識した設計/構成がますます重要になってくる未来が見えました。