tomoima525's blog

Androidとか技術とかその他気になったことを書いているブログ。世界の秘密はカレーの中にある!サンフランシスコから発信中。

Android App Bundle/Dynamic feature modulesにみるモジュール化の未来

f:id:tomoima525:20180510085805p:plain

Google I/O 2018で発表された新機能であるAndroid App BundleとDynamic feature modulesが大規模アプリのペインポイントを解決しそうなので、早速利用/サンプルプロジェクトを触ってみた所感などを書きます。(5/9Dynamic featureのテスト方法について更新しました)

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で簡単に確認できます。

github.com

ここにあるjarファイルをダウンロードし、手元のNDKを利用している会社のプロジェクトで生成したaabに実行してみた結果が以下です。

f:id:tomoima525:20180510090702j:plain:w300

言語リソースなどの大量の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でバイナリ化されていました。

f:id:tomoima525:20180510090859p:plain:w300

あとはこのファイルを通常のapkと同じようにPlayStoreにアップロードすれば、最適なapkが配信されるようになります。ユーザーが本当にapkを受け取れるのかを確認する方法としては、今回導入されたinternal test にまずはデプロイすることが推奨されています。internal testはalphaの更に前のテストに利用できるテスター機能です。デプロイしてから1分ほどの短い時間でテストユーザーに配信されるのが特徴です。2018/5/8現在すでにPlayStoreで設定可能になっています。

f:id:tomoima525:20180510091104p:plain:w300

実際にGoogle IOにてデモを見たところ、一瞬で配信されていました。ただしPlayStoreなので、testにデプロイする度にbuild versionをアップデートする必要がある点は気をつけたいです。

support.google.com

2018/5/8時点ではApp Bundleを実装するためにはIDEAndroid Studio Canary 14が必要です。手元のプロジェクトで試した際はdrawable vectorxmlから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時に表示されるタイトル。basestrings.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" を作る
    f:id:tomoima525:20180510091413p:plain:w300

  • On demandの設定を追加
    f:id:tomoima525:20180510091426p:plain:w300

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()
}

実際のサンプルアプリはこちらにあります。

github.com

さて、この機能の紹介時にいくつかの疑問点があったため、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
  }

(5/9追記) さらに確認したところ、build configurationにてapkにdynamic feature modules を含むか含まないかを選択できるそうです。これでチェックを外すと上記の処理はfalseになり、ダウンロードタスクが走るようになります。

f:id:tomoima525:20180511024005p:plain

プロダクションでのテストは?

実際のダウンロード処理の挙動を確認したい場合はPlayStoreのinternal testを利用する必要があります。ローカルでテストする方法は開発中だそうです。

ビルドタイム、ランタイムの影響は?

ビルドタイムはモジュール化によりパラレル化できるので、スピードはあがるはず、ランタイム時はmultidexと同じでdexファイルを各apkファイルから取得するので、若干のオーバーヘッドがあるそうです。

まとめ: アプリサイズの削減のためにモジュール化を意識した設計は必須

長期間アプリを開発/運用していると、さまざまな機能追加などによりアプリサイズは肥大化していきます。 実際に転送量は莫大なものとなっているようで、Android App Bundle/Dynamic feature modulesはGoogleの本気がうかがえます。

アプリサイズを小さくすることはユーザーにとっても望ましいものです。縦軸が不明なんですがアプリサイズが増加するとリニアにPlayStoreでのDL率が下がるそうな。

f:id:tomoima525:20180508170725j:plain:w300

Dynamic feature modulesを最大限に利用するために、依存性/モジュール化を意識した設計/構成がますます重要になってくる未来が見えました。