Save 37% off PRO during our Black Friday Sale! »

マルチモジュールAndroidアプリケーション / Multi-module Android Application

13d936e697fe0f4fa96f926d0a712f6c?s=47 Sansan
PRO
February 08, 2019

マルチモジュールAndroidアプリケーション / Multi-module Android Application

■イベント
DroidKaigi
https://droidkaigi.jp/2019/

■登壇概要
タイトル:
マルチモジュールAndroidアプリケーション

登壇者:
Sansan株式会社 Eight事業部 山本純平

▼Sansan Builders Box
https://buildersbox.corp-sansan.com/

13d936e697fe0f4fa96f926d0a712f6c?s=128

Sansan
PRO

February 08, 2019
Tweet

Transcript

  1. マルチモジュール Androidアプリケーション DroidKaigi2019 day2 room2 15:40 Jumpei Yamamoto

  2. 山本純平 Sansan株式会社 Eight事業部 Engineering group Eight Androidアプリの開発 「Kotlinイン・アクション」の翻訳に参 加

  3. 山本純平 twitter: @boohbah github: yamamotoj

  4. None
  5. Eight Android版 • 40個のモジュールにて構成され たプロジェクト • この経験からアプリケーションのマ ルチモジュール化についてお話し たいと思います。

  6. Agenda • なぜマルチモジュールにするのか? • マルチモジュールでビルドを高速化する • アプリの設計とマルチモジュール化 • マルチモジュールアプリケーションを実装する

  7. モジュール • Androidのプロジェクト内でコンパイル単 位を分割することができる • 分割したモジュール間での依存関係を定 義できる • 循環した依存は定義できない

  8. モジュールの種類 Application Module APKとしてリリース可能 Library Module AARとしてリリース可能 Dynamic Feature Module

    インストール後に追加できる
  9. なぜマルチモジュールにするのか?

  10. ビルドを高速化 • マルチモジュールにすることでビルドを高速化すること ができる。 • 後ほど詳しく説明します。

  11. ソースコードの依存関係を強制できる • モジュール間の循環依存は許されないため、うまく使うこ とでソースコードの依存関係を強制することができる。 • 後ほど詳しく説明します。

  12. モジュール毎にテストを実行できる • モジュール毎にUnit Test, Android Testを独立に実 行できる • テストの度に全体をビルドする必要がなく、必要なモジュー ルのみをビルドするので、実装→テストのサイクルを高速

    に回すことができる
  13. 複数のアプリケーションの管理を行える product avorによる複数アプリケーションの管理 flavorDimensions "sourceSet" productFlavors { demo { dimension

    "sourceSet" } production { dimension "sourceSet" } } • build variant をdemoにする と、production のソースは管理 外になってしまう • リファクタリング が効かない
  14. 複数のアプリケーションの管理を行える • すべてのコードでリファクタリングが適用される :app_demo :app_production :library_demo :library_production :library_common

  15. 複数のアプリケーションの管理を行える :app_demo :app_production :library_demo :library_production :library_common :app_instant instant appとして一部機能を提供

  16. Kotlinのinternal宣言 • Kotlinで新たに追加された可視性修飾子 • internalで宣言されたクラス、メソッド、プロパティなど は同じモジュール内からのみ参照可能 • (packageではなく)モジュールを使った可視性の制御 が可能になった internal

    class Hoge { … }
  17. Dynamic Feature Module • インストール時に使用しない機能を、 必要になったタイミングで追加モ ジュールとしてダウンロードすること ができる • インストール時に容量を削減

    :base_module :dynamic_module あとから ダウンロード
  18. まとめ • ビルドを高速化 • モジュールごとにテストを実行できる • モジュールを使って複数アプリケーションの管理を行える • ソースコードの依存関係を強制できる •

    Kotlinではモジュールでのコードの可視性を定義できる • Dynamic Feature Moduleでインストール時の容量を 削減できる
  19. マルチモジュールによる ビルドの高速化

  20. マルチモジュールによるビルドの高速化 • Android Gradle Plugin3.0以降の動作のしくみ • ビルド時間の計測実験 • pluginやannotation processorがある場合の高速

    化テクニック • モジュール分割によって効果的に高速化する方法
  21. Android Gradle Plugin 3.0 以降の動作の仕組み

  22. Android Gradle Plugin 3.0.0 • マルチモジュールでのビルドが 高速化 • 依存関係の定義の方法が •

    旧) compile • 新) implementation, api
  23. compile指定 (before 3.0)

  24. compile指定 (before3.0) dependencies{ compile: module1 } dependencies{ compile: module2 }

    :app :module1 :module2 “compile”Ͱ ґଘؔ܎Λࢦఆ
  25. compile指定 (before3.0) dependencies{ compile: module1 } dependencies{ compile: module2 }

    :app :module1 :module2 参照可能
  26. compile指定 (before3.0) dependencies{ compile: module1 } dependencies{ compile: module2 }

    :app :module1 :module2 参照可能
  27. compile指定 (before3.0) dependencies{ compile: module1 } dependencies{ compile: module2 }

    :app :module1 :module2 มߋ
  28. compile指定 (before3.0) dependencies{ compile: module1 } dependencies{ compile: module2 }

    :app :module1 :module2 変更 コンパイル
  29. compile指定 (before3.0) dependencies{ compile: module1 } dependencies{ compile: module2 }

    :app :module1 :module2 変更 コンパイル コンパイル
  30. implementation指定 (after3.0)

  31. implementation指定 (after3.0) dependencies{ implementation: module1 } dependencies{ implementation: module2 }

    :app :module1 :module2 “implementation”Ͱ ґଘؔ܎Λࢦఆ
  32. implementation指定 (after3.0) dependencies{ implementation: module1 } dependencies{ implementation: module2 }

    :app :module1 :module2 参照可能
  33. implementation指定 (after3.0) dependencies{ implementation: module1 } dependencies{ implementation: module2 }

    :app :module1 :module2 参照不可
  34. implementation指定 (after3.0) dependencies{ implementation: module1 } dependencies{ implementation: module2 }

    :app :module1 :module2 変更
  35. implementation指定 (after3.0) dependencies{ implementation: module1 } dependencies{ implementation: module2 }

    :app :module1 :module2 変更 コンパイル
  36. implementation指定 (after3.0) dependencies{ implementation: module1 } dependencies{ implementation: module2 }

    :app :module1 :module2 変更 コンパイル コンパイル されない
  37. api指定 (after3.0)

  38. api指定 (after3.0) dependencies{ api: module1 } dependencies{ api: module2 }

    :app :module1 :module2 “api”Ͱ ґଘؔ܎Λࢦఆ
  39. api指定 (after3.0) dependencies{ api: module1 } dependencies{ api: module2 }

    :app :module1 :module2 参照可能
  40. api指定 (after3.0) dependencies{ api: module1 } dependencies{ api: module2 }

    :app :module1 :module2 参照可能
  41. api指定 (after3.0) dependencies{ api: module1 } dependencies{ api: module2 }

    :app :module1 :module2 มߋ
  42. api指定 (after3.0) dependencies{ api: module1 } dependencies{ api: module2 }

    :app :module1 :module2 変更 コンパイル
  43. api指定 (after3.0) dependencies{ api: module1 } dependencies{ api: module2 }

    :app :module1 :module2 変更 コンパイル コンパイル
  44. 挙動の違い compile(before3.0) api(after3.0) implementation (after3.0) モジュール の参照 間接的に依存しているすべ てのモジュールも参照できる 直接依存しているモジュールの

    みが参照可能 ビルドの 伝播 間接的に依存するすべての モジュールのコンパイルが実 行される 直接依存するモジュールのみが コンパイルされる
  45. Android Gradle Pluginまとめ • 依存関係の指定を”compile”から”implementation” にすることで無駄な再コンパイルを抑えることができる • “compile”と同等の挙動で使いたければ”api”指定を 使う

  46. ビルド時間の計測実験

  47. シングルモジュール vs マルチモジュールで ビルド時間の比較をしてみる • 1Activity + 2000クラス • 1クラスあたり6メソッド

    • dagger2によるAnnotation processing • @Injectアノテーション • それぞれのクラスに対応するFactoryと Injectorクラスがビルド時に生成される • ソースコードは以下に公開 • https://github.com/yamamotoj/ android_multi_module_experiment class Foo00000 @Inject constructor(){ fun method0() {} fun method1() { method0() } fun method2() { method1() } fun method3() { method2() } fun method4() { method3() } fun method5() { method4() } }
  48. モジュール構成の違う3種類のアプリを作っ てビルドにかかる時間を比較 • フルビルド • インクリメンタルビルド

  49. ケース1: シングルモジュール • ひとつのモジュールに MainActivityと2000クラス が全て含まれている :app MainActivity 2000class

  50. ケース2: 直列マルチモジュール • :appモジュールにMainActivity • それ以外に4つのモジュールを持ちそ れぞれに500クラスずつ含まれてい る • 各モジュールは直列でそれぞれが次

    のモジュールに依存する :app :module4 :module3 :module2 :module1
  51. ケース3: 並列マルチモジュール • :appモジュールにMainActivity • それ以外に4つのモジュールを持ちそれぞれに500クラスずつ含ま れている • :app以外のモジュールは互いに依存することなく:appモジュールの みが各モジュールに依存している

    :app :module1 :module2 :module3 :module4
  52. ビルド条件 • macbook pro 2017 13inch • Core i7 (4core)

    • メモリ32GB • Android Studio 3.3 • Gradle 5.1.1 • Android Gradle Plugin 3.3.0 • gradle.propertiesの設定 • org.gradle.parallel = true • org.gradle.daemon = true • org.gradle.caching = true • kotlin.incremental.sePreciseJavaTracking = true
  53. フルビルドで比較

  54. フルビルド • それぞれのビルド毎にcleanしてビルドを実行 • 5回試行した結果の平均をとる ./gradlew assembleDebug —no-build-cache —scan

  55. フルビルド時間(秒) Ϗϧυ࣌ؒʢඵʣ γϯάϧϞδϡʔϧ ௚ྻϚϧνϞδϡʔϧ ฒྻϚϧνϞδϡʔϧ T T T

  56. フルビルド時間(秒) Ϗϧυ࣌ؒʢඵʣ γϯάϧϞδϡʔϧ ௚ྻϚϧνϞδϡʔϧ ฒྻϚϧνϞδϡʔϧ T T T 若干速い

  57. ビルド時に何が起こったかを確認してみる • gradle build scan • build結果をgradleのサーバに送信し解析 • 送信先は scans.gradle.org

    • build時に —scan オプションを追加し、利用規約に 同意することで利用可能 ./gradlew assembleDebug —no-build-cache —scan
  58. None
  59. None
  60. シングルモジュールのビルド結果 36s https://scans.gradle.com/s/5v3vnsh4lmepg

  61. シングルモジュールのビルド結果 :appモジュールの kapt stub作成 6.8s 36s https://scans.gradle.com/s/5v3vnsh4lmepg

  62. シングルモジュールのビルド結果 :appモジュールの kapt stub作成 6.8s :appモジュールの Annotation Processing 8.4s 36s

    https://scans.gradle.com/s/5v3vnsh4lmepg
  63. シングルモジュールのビルド結果 :appモジュールの kapt stub作成 6.8s :appモジュールの Annotation Processing 8.4s :appモジュールの

    Kotlinコンパイル 7.0s 36s https://scans.gradle.com/s/5v3vnsh4lmepg
  64. シングルモジュールのビルド結果 :appモジュールの kapt stub作成 6.8s :appモジュールの Annotation Processing 8.4s :appモジュールの

    Kotlinコンパイル 7.0s classからdexへ の変換 7.0s 36s https://scans.gradle.com/s/5v3vnsh4lmepg
  65. 直列マルチモジュールのビルド結果 39s https://scans.gradle.com/s/o4pvtp3dx5nvi

  66. 直列マルチモジュールのビルド結果 :module1 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 9.3s 39s https://scans.gradle.com/s/o4pvtp3dx5nvi

  67. 直列マルチモジュールのビルド結果 :module1 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 9.3s :module2 kapt

    stub作成
 Annotation Processing
 Kotlinコンパイル 7.0s 39s https://scans.gradle.com/s/o4pvtp3dx5nvi
  68. 直列マルチモジュールのビルド結果 :module1 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 9.3s :module2 kapt

    stub作成
 Annotation Processing
 Kotlinコンパイル 7.0s :module3 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 7.0s 39s https://scans.gradle.com/s/o4pvtp3dx5nvi
  69. 直列マルチモジュールのビルド結果 :module1 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 9.3s :module2 kapt

    stub作成
 Annotation Processing
 Kotlinコンパイル 7.0s :module3 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 7.0s :module4 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 6.9s 39s https://scans.gradle.com/s/o4pvtp3dx5nvi
  70. 並列マルチモジュールの結果 31s https://scans.gradle.com/s/dhvaghy7in2vq

  71. 並列マルチモジュールの結果 :module1-4の kapt stub作成 約4.7s 31s https://scans.gradle.com/s/dhvaghy7in2vq

  72. 並列マルチモジュールの結果 :module1-4の kapt stub作成 約4.7s :module1-4の Annotation Processing 約8.4s 31s

    https://scans.gradle.com/s/dhvaghy7in2vq
  73. 並列マルチモジュールの結果 :module1-4の kapt stub作成 約4.7s :module1-4の Annotation Processing 約8.4s :module1-4の

    Kotlinコンパイル 約5.4s 31s https://scans.gradle.com/s/dhvaghy7in2vq
  74. シングル vs 並列 シングルモジュールのビルド 並列マルチモジュールのビルド 36s 31s

  75. フルビルド時間(秒) Ϗϧυ࣌ؒʢඵʣ γϯάϧϞδϡʔϧ ௚ྻϚϧνϞδϡʔϧ ฒྻϚϧνϞδϡʔϧ T T T

  76. フルビルドの結果 • モジュール分割により、コンパイルやAnnotation Processingの順序は大きく変わる • コンパイルやAnnotation Processingを並列に実行 することでビルド速度は若干向上 • シングルモジュールでもコンパイルやAnnotation

    Processingの並列処理はなされており、マルチモジュー ルで並列度を上げたところで劇的に速度が向上するわけ ではない
  77. インクリメンタルビルドで比較

  78. インクリメンタルビルド • それぞれのプロジェクトでソースコードを一行だけ変更し て、ビルド時間を比較する • 5回試行した結果の平均をとる ./gradlew assembleDebug —scan class

    Foo00000 @Inject constructor(){ fun method0() {} fun method1() { method0() } fun method2() { method1() } fun method3() { method2() } fun method4() { method3() } fun method5() { method4() } fun method(){} } クラスにメソッドを追加した り削除したりしてDaggerの Annotation Processing が動作するように
  79. 変更箇所: シングルモジュール • :appモジュールのコードを一行 変更 :app MainActivity 2000class

  80. 変更箇所: 直列マルチモジュール • 依存の最も深い :module1のコード を一行変更する :app :module4 :module3 :module2

    :module1
  81. 変更箇所: 並列マルチモジュール • :appモジュールが依存する:module1を一行変更 :app :module1 :module2 :module3 :module4

  82. インクリメンタルビルド時間 ビルド時間(秒) シングルモジュール 直列マルチモジュール 並列マルチモジュール 7s 10.2s 14.2s

  83. シングルモジュールのビルド 14.8s https://scans.gradle.com/s/32k6o3avjen7i

  84. シングルモジュールのビルド 14.8s :appモジュー ルのkapt stub作成 2.9s https://scans.gradle.com/s/32k6o3avjen7i

  85. シングルモジュールのビルド 14.8s :appモジュー ルのkapt stub作成 2.9s :appモジュールのAnnotation Processing 8.3s https://scans.gradle.com/s/32k6o3avjen7i

  86. シングルモジュールのビルド 14.8s :appモジュー ルのkapt stub作成 2.9s :appモジュールのAnnotation Processing 8.3s :appモジュー

    ルのKotlin コンパイル 1.3s https://scans.gradle.com/s/32k6o3avjen7i
  87. 直列マルチモジュールのビルド 10s https://scans.gradle.com/s/v2xy6oultdawm

  88. 直列マルチモジュールのビルド 10s https://scans.gradle.com/s/v2xy6oultdawm :module1
 kapt stubの作成
 Annotation processing 3.7s

  89. 直列マルチモジュールのビルド 10s https://scans.gradle.com/s/v2xy6oultdawm :module2
 kapt stubの作成
 Annotation processing 3.7s :module1


    kapt stubの作成
 Annotation processing 3.7s
  90. 直列マルチモジュールのビルド 変更されたモジュールと直接依存している モジュールのみが再コンパイル +Annotation processing されている :app :module4 :module3 :module2

    :module1 :module1
 3.7s :module2
 3.7s
  91. 並列マルチモジュールのビルド 7.4s

  92. 並列マルチモジュールのビルド :module1
 kapt stubの作成
 Annotation processing 4.3s 7.4s

  93. 並列マルチモジュールのビルド :module1
 kapt stubの作成
 Annotation processing 4.3s :app
 Annotation processing

    1.2s 7.4s
  94. 並列マルチモジュールのビルド :app :module1 :module2 :module3 :module4 :app
 1.9s :module1 4.3s

    :appモジュールの
 Annotation Processingが再実行
  95. インクリメンタルビルド時間 ビルド時間(秒) シングルモジュール 直列マルチモジュール 並列マルチモジュール 7s 10.2s 14.2s

  96. Google I/O 2017でのお話 • Google I/O 2017 speeding up your

    build • https://www.youtube.com/watch?v=7ll-rkLCtyk
  97. Google I/O 2017でのお話 • Javacはインクリメンタルだが Annotation processingはイン クリメンタルなビルドには対応して いない •

    Annotation processingを使っ ている場合は一つのクラスを変更し ただけでも全てを再コンパイルしな ければならない :app
  98. Google I/O 2017でのお話 • 小さなモジュールに分割することで Annotation processingの実行 範囲を最小限におさえることができ る :app

    :module1 :module2
  99. インクリメンタルビルド時間 ビルド時間(秒) シングルモジュール 直列マルチモジュール 並列マルチモジュール 7s 10.2s 14.2s

  100. インクリメンタルビルドの結果 • 変更されたモジュールとそれを直接参照しているモジュー ルのみのAnnotation processingとコンパイルが実 行される • Annotation processingは実行時に影響するモジュー ル全体で再実行されるため、影響するもモジュールが小

    さいほうがビルドが速い
  101. 特殊なpluginがある場合の高速 化テクニック

  102. • モバイルアプリ向けデータベース、ORM • Androidでも利用可能

  103. Realmのオブジェクト定義 public class Article extends RealmObject { @PrimaryKey public String

    id; public String content; } JAVA
  104. Realmのオブジェクト定義 public class Article extends RealmObject { @PrimaryKey public String

    id; public String content; } JAVA 永続化されるデータを Javaのフィールドとし て定義
  105. RealmObjectのデータ取得 Article article = realm.where(Article.class) .equals(“id”, “1”) .findFirst(); String content

    = article.content; JAVA
  106. RealmObjectのデータ取得 Article article = realm.where(Article.class) .equals(“id”, “1”) .findFirst(); String content

    = article.content; JAVA この時点では空のオプジェクトの みが生成され、データはロードさ れていない
  107. RealmObjectのデータ取得 Article article = realm.where(Article.class) .equals(“id”, “1”) .findFirst(); String content

    = article.content; JAVA フィールドにアクセスされた時点 でcontentのデータが遅延ロー ドされる
  108. なぜこんなことが可能か? public class Article extends ReamObject { @PrimaryKey public String

    id; public String content; } JAVA
  109. なぜこんなことが可能か? public class Article extends ReamObject { @PrimaryKey public String

    id; public String content; public String realmGet$content() { return row.getString(2); } /* ... */ } JAVA コードの追加
  110. なぜこんなことが可能か? String content = article.content; JAVA

  111. なぜこんなことが可能か? String content = article.content; JAVA String content = article.realmGet$content();

    JAVA 呼び出し元のコードを書き換え
  112. Realmのコード生成 + 書き換え • RealmObjectの定義クラスに、データベースから値を ロードするメソッドを追加 • RealmObjectのフィールドを参照している箇所を、デー タベースから値をロードするコードに書き換え

  113. Realmのコード生成 + 書き換え • RealmObjectの定義クラスに、データベースから値を ロードするメソッドを追加 • RealmObjectのフィールドを参照している箇所を、デー タベースから値をロードするコードに書き換え Bytecode

    weaving
  114. Bytecode weaving • javaファイルのコンパイル後、classファイルのバイトコー ドを書き換える .java .kt .class όΠτίʔυͷॻ͖׵͑

  115. realm gradle plugin buildscript { dependencies { classpath "io.realm:realm-gradle-plugin:5.8.0" }

    } ϧʔτͷbuild.gradle apply plugin: ‘com.android.application’ apply plugin: ‘kotlin-android’ apply plugin: ‘realm-android’ :appͷbuild.gradle
  116. realm gradle plugin buildscript { dependencies { classpath "io.realm:realm-gradle-plugin:5.8.0" }

    } ϧʔτͷbuild.gradle apply plugin: ‘com.android.application’ apply plugin: ‘kotlin-android’ apply plugin: ‘realm-android’ :appͷbuild.gradle ここでpluginを指定す ることでBytecode weavingが実行され る
  117. モジュールの視点で考える • RealmObjectにメソッド を追加 • RealmObjectのフィール ドを参照している箇所をデー タをロードするメソッドに 書き換える :app

    apply plugin: 'realm-android'
  118. モジュールの視点で考える • RealmObjectにメソッド を追加 • RealmObjectのフィール ドを参照している箇所をデー タをロードするメソッドに 書き換える :app

    参照箇所の検索に 時間がかかる apply plugin: 'realm-android'
  119. モジュールの切り出しで高速化

  120. STEP1: Realm専用のモジュールを作成 し、Realm関連のファイルを移動 :app

  121. STEP1: Realm専用のモジュールを作成 し、Realm関連のファイルを移動 :app :realm

  122. STEP2: RealmObjectの定義クラスにgetter/ setterを定義し、フィールドをprivateにする :realm public class Article extends ReamObject {

    @PrimaryKey public String id; public String content; } :app
  123. STEP2: RealmObjectの定義クラスにgetter/ setterを定義し、フィールドをprivateにする :realm public class Article extends ReamObject {

    @PrimaryKey private String id; private String content; public Strig getId(){ return id; } public void setId(String id){ this.id = id; } .. .. getter/ setterを 定義 :app
  124. STEP2: RealmObjectの定義クラスにgetter/ setterを定義し、フィールドをprivateにする :realm public class Article extends ReamObject {

    @PrimaryKey private String id; private String content; public Strig getId(){ return id; } public void setId(String id){ this.id = id; } .. .. データベースから ロードするメソッ ドへの書き換え はgetter/ setterの内部の みに限られる :app
  125. STEP3: realm-pluginの指定をRealm 専用モジュールに限定 :realm apply plugin: ‘com.android.application’ apply plugin: ‘realm-android’

    :appͷbuild.gradle :app
  126. STEP3: realm-pluginの指定をRealm 専用モジュールに限定 :realm apply plugin: ‘com.android.application’ apply plugin: ‘realm-android’

    :appͷbuild.gradle :app
  127. STEP3: realm-pluginの指定をRealm 専用モジュールに限定 :realm apply plugin: ‘com.android.application’ apply plugin: ‘realm-android’

    :appͷbuild.gradle apply plugin: ‘com.android.library’ apply plugin: ‘realm-android’ :realmͷbuild.gradle :app
  128. Bytecode weavingが適用される範囲 :app :app :realm before after

  129. Eightアプリのフルビルドにかかる時間 :realm before after 6m46s 4m27s 29%減少

  130. まとめ • ビルド時に実行する特殊なpluginがある場合に、その pluginを必要とする部分だけモジュールとして切り出し すことで、ビルド時にかかる時間を最小限に抑えること ができる • pluginだけでなくannotation processorが必要な ライブラリでも同様

  131. 効果的にビルドを高速化する

  132. 再掲: インクリメンタルビルドの結果 • 変更されたモジュールとそれを直接参照しているモジュー ルのみのAnnotation processingとコンパイルが実 行される • Annotation processingは実行時に影響するモジュー

    ル全体で再実行されるため、影響するもモジュールが小 さいほうがビルドが速い
  133. 再掲: インクリメンタルビルドの結果 • 変更されたモジュールとそれを直接参照しているモジュー ルのみのAnnotation processingとコンパイルが実 行される • Annotation processingは実行時に影響するモジュー

    ル全体で再実行されるため、影響するもモジュールが小 さいほうがビルドが速い モジュールの依存関係の構造によって ビルドの速度が変わってくる
  134. ビルドが高速化しない構造

  135. 大きなモジュールが依存 • サイズの大きなモジュール が小さなモジュールに依存 している • :module1が変更されても サイズが大きいのでビルド は遅い •

    :module2が変更されて も:module1の再ビルドが 走ってしまう :module1 :module2
  136. 被リンクモジュールの数が多い • 依存元モジュールの 数が多いほどビル ドに時間がかかる • めったに変更しない フレームワーク的な モジュールやユーティ リティならあり

    :module1 :module2 :module3 :module4 :module5 :module6
  137. プロジェクト内の依存構造がスパゲッティ • 依存関係が多く存在してい るプロジェクトは、いくらモ ジュール分割をしてもビル ドは効果的に高速化しない :module1 :module2 :module3 :module4

    :module5 :module6
  138. ビルドの高速化に効果的な構造

  139. 小さなモジュールが依存している :small :module1

  140. 依存元が1つ • 依存元が1つの構造であればその関係が増えても局所的 な再コンパイルで済む :module4 :module6 :module9 :module5 :module7 :module3

    :module2 :module8 :modle1 :small
  141. シンプルな依存関係の構造 • 依存元のモジュールが一 つであれば、再コンパイル は最小限に抑えられる :module1 :module2 :module3 :module4 :module5

    :module6 :module7
  142. 効果的なビルドの高速化まとめ • モジュール分割によって効果的にビルドを高速化するた めにはモジュール間の依存関係をシンプルに保ち、でき るだけ少ない(一つの)モジュールからのみ依存される構 造を作り出すことが重要

  143. もう一つ、ビルドの高速化で重要なこと • Android Studio, gradle, kotlin pluginなどは常 に最新のをものをつかう • ビルド時のマルチモジュールの最適化は日々進化

    • 並列ビルド • ビルドキャッシュ • 不要な処理の最適化
  144. Gradle 4.7のリリースノート • Incremental annotation processingをサポート • https://docs.gradle.org/4.7/release-notes.html? _ga=2.231409431.904362546.1523996697-559636575.1501515251# incremental-annotation-processing

  145. dagger2.18のリリースノート • https://github.com/google/dagger/releases/tag/dagger-2.18

  146. kaptはまだ未対応(issueのみ) • Let’s vote! • https://youtrack.jetbrains.com/issue/KT-23880

  147. アプリの設計とマルチモジュール 化

  148. モジュール分割の方向性 • 水平方向の分割 • レイヤーごと • 垂直方向の分割 • 機能ごと

  149. モジュール分割の方向性 • 水平方向の分割 • レイヤーごと • 垂直方向の分割 • 機能ごと プレゼンテーション層

    ビジネスロジック層 データ層
  150. モジュール分割の方向性 • 水平方向の分割 • レイヤーごと • 垂直方向の分割 • 機能ごと 機能1

    機能2 機能3
  151. 水平方向のモジュール分割

  152. Architecting Android...The clean way? https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/

  153. アプリケーションを3つの層に分割 • プレゼンテーション層 • ドメイン層 • データ層 プレゼンテーション層 ドメイン層 データ層

  154. プレゼンテーション層 • ビューやアニメーションに 関するロジック • Activity, Fragment • MVP, MVVM

    プレゼンテーション層 ドメイン層 データ層
  155. ドメイン層 • ビジネスルールに関するロ ジック • Androidに依存しない純粋 なJava(Kotlin)のモジュー ル • 他のレイヤと接続するために

    はインターフェイスを用いる プレゼンテーション層 ドメイン層 データ層
  156. データ層 • アプリケーションが必要とす るデータを提供 • ドメイン層のインターフェイ スに基づきリポジトリパター ンで実装 プレゼンテーション層 ドメイン層

    データ層
  157. クリーンアーキテクチャ • 円の内側にドメイン モデル • 円の外側にUI、デー タベース • ソースコードの依存 性は内側にだけ向

    かっていなければ ならない
  158. レイヤ構造をモジュールで表現 :app :domain :data

  159. 依存関係 :app :domain :data ドメイン層はAndroidに依存しない データ層はドメイン層のインターフェイ スを使用

  160. 依存関係 :app :domain :data UIはdomainのみに依 存するべき

  161. 依存関係 :ui :domain :data :app

  162. 依存関係 :ui :domain :data :app :app͔ΒUIؔ࿈ͷ ίʔυΛಠཱ

  163. 依存関係 :ui :domain :data :app :app͔ΒDIʹΑͬ ͯ:domainͷΠϯλʔ ϑΣΠεʹର͠ ͯ:dataͷ࣮૷Λ஫ೖ

  164. 水平方向のモジュール分割まとめ • 一般的なAndroidのレイヤードアーキテクチャをモジュー ルに当てはめて、上位モジュールが下位モジュールに依 存しないことを保証できる • プレゼンテーション層のモジュールを :app モジュールか ら分離し、DIを導入することによってより独立性を保つ

    モジュールの構造を作り出すことができる
  165. 垂直方向のモジュール分割

  166. 垂直方向のモジュール分割 • 機能ごとにモジュールを 分割する 機能1 機能2 機能3

  167. Eightの例 メイン画面 投稿詳細画面 人物詳細画面

  168. Eightの例 メイン画面 投稿詳細画面 人物詳細画面 :component_ main :component_ post_detail :component_ person_detail

  169. 機能別にモジュール分割するメリット • 機能別にコードがまとまっているので理解しやすい • 修正の影響範囲を最低限にまとめることができる メイン画面 投稿詳細画面 人物詳細画面 :component_ main

    :component_ post_detail :component_ person_detail
  170. 機能別モジュールの依存関係 投稿詳細画面 人物詳細画面

  171. 機能別モジュールの依存関係 投稿詳細画面 人物詳細画面 人物をタップして詳 細を開く

  172. 機能別モジュールの依存関係 投稿詳細画面 人物詳細画面 その人物の投稿

  173. 機能別モジュールの依存関係 投稿詳細画面 人物詳細画面 その人物の投稿を タップして詳細を開 く

  174. 機能別モジュールの依存関係 投稿詳細画面 人物詳細画面 循環依存

  175. 依存関係が循環依存するケース 投稿詳細画面 人物詳細画面 :component_ post_detail :component_ person_detail 人物詳細画面の起動 投稿詳細画面の起動

  176. 相互のIntentを取得するインターフェイス を共通のモジュールで定義 投稿詳細画面 人物詳細画面 :component_ post_detail :component_ person_detail interface IntentResolver{

    fun getPersonDetailActivityIntent(): Intent fun getPostDetailActivityIntent(): Intent } :common
  177. app:モジュールにて実装クラスを定義 :component_ post_detail :component_ person_detail class IntentResolverImpl: IntentResolver { override

    fun getPersonDetailActivityIntent(): Intent = Intent(…) override fun getPostDetailActivityIntent(): Intent = Intent(…) } app: お互いのモジュールを参照
  178. DIにて解決 :component_ post_detail :component_ person_detail app: :common interface IntentResolver{} class

    IntentResolverImpl: IntentResolver inject inject
  179. Eightの各機能の依存関係 :component_ main :component_ post_detail :component_ person_detail :component_ create_post :component_

    chat :component_ my_page :component_ camera :component_ search :component_ on_boarding
  180. DIを導入 • 循環依存を解決 • 各機能の依存関係を atに配置 • 各機能間で本当に必要な連携のみを抽出できる • ビルド速度面でもメリット

    :component_ main :component_ post_detail :component_ person_detail :component_ create_post :component_ chat :component_ my_page :component_ camera :component_ search :component_ on_boarding :app
  181. 垂直方向の機能分割まとめ • 機能別にモジュールを分割することによって、コードの影 響範囲を最小限にとどめることができる • 機能間の連携に必要なAPIをDIをつかって外部から注 入することによって、各機能間で直接依存関係をもつこ となく、互いに疎な関係を保つことができる

  182. Eightの例 メイン画面 人物詳細画面 :component_ main :component_ post_detail :domain_ main :repository_

    main :domain_ post_detail :repository post_detail :app
  183. 既存のアプリをマルチモジュール 化する

  184. シングルモジュールのアプリ :app

  185. 既存のモジュールの分割 • yak shaving • ある問題を解こうと思った ら別の問題が出てきて、そ れを解こうと思ったらさら に別の問題が出てきて… ということが延々と続く状

  186. 詳しくは…

  187. 細かくアプリを切り出す? • モノリシックなアプリか らモジュールを切り出し ていくのは大変 • モジュールとして切り出 してもビルドの高速化 には寄与しないかも :app

    :module :module :module
  188. Realmを使用している場合 • realmを使用している場 合は、RealmObjectの 定義クラスをモジュール として切り出すことでビ ルドが高速化する :app :realm

  189. まず最小限のApplicationモジュールとそ れ以外のコードを切り離す • 最小限のコードを残して、 全ての実装を別モジュール に移動する • :appモジュールには Application classと

    Application scopeの DI Component :legacy :app
  190. :appモジュールの主な機能 • Applicationクラス • サブモジュール間の依存関係 を解決する • ドメイン層を介したプレゼ ンテーション層とデータ層 の実装の解決

    • 各機能間の連携を抽象化し て解決 • サブモジュールのビルドが速 くなるようにできるだけ軽く :legacy :app
  191. dagger2を使用してるケース class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState)

    (application as MyApplication) .component .mainActivityComponentBuilder() .build() .inject(this) } }
  192. dagger2を使用してるケース class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState)

    (application as MyApplication) .component .mainActivityComponentBuilder() .build() .inject(this) } } Application classΛࢀর͠ ͍ͯΔ
  193. :legacy :app Application, Activity, Component の関係 MyApplication AppComponent MainActivityComponent MainActivity

  194. :legacy :app Application, Activity, Component の関係 MyApplication AppComponent MainActivityComponent MainActivity

    循環参照
  195. Dagger2 Android Support MyApplication AppComponent MainActivityComponent MainActivity この参照を断ち切ることができる

  196. :legacy :app dagger2 Android support MyApplication AppComponent MainActivityComponent MainActivity MainActivityBindingModule

  197. シンプルな:appモジュールの分離 :legacy :app

  198. シンプルな:appモジュールの重 要性

  199. 複数アプリケーションの管理を行うために • 機能を含まず、アプリの構成を決定するためのモジュールが必要 :app_demo :app_production :library_demo :library_production :library_common

  200. クリーンアーキテクチャを保証するために • 依存関係を解決するための上位モジュールが必要 :ui :domain :data :app

  201. 分割した機能の連携を解決するために • 連携の解決をするための上位モジュールが必要 :component_ main :component_ post_detail :component_ person_detail :component_

    create_post :component_ chat :component_ my_page :component_ camera :component_ search :component_ on_boarding :app
  202. :appモジュールを分離したあとは? :legacy :app

  203. :appモジュールを分離したあとは? :app • 最低限必要となる共通ライ ブラリのモジュールは切り 出す :legacy :common

  204. 新機能だけを別モジュールとして実装 • 新機能の開発時は ビルドが早くて快適 • できるだけ:legacy モジュールには触ら ないのがベター :legacy :app

    :new_feature :common
  205. 既存のアプリをマルチモジュール化するま とめ • まず、最小限の実装のみを含む:appモジュールとそれ以 外に分割する • dagger2を使っている場合はAndroid supportの機 能を使ってApplicationクラスと各Activityとの循環 依存を解決する

    • 既存のモジュールの分割はつらいので、できるだけ触ら ないで進める方法を考える
  206. まとめ • Annotation processingが存在する場合、モジュール間の依存関 係を適切に設定することによってマルチモジュール化によってビルド を高速化することができる • Incremental annotation processingの実装によってマルチ

    モジュールはビルドの高速化に対してあまり意味を持たなくなるか も • 水平方向、垂直方向にモジュールを分割することによってレイヤの依 存関係を強制し、関心事を分離することができる • 既存のアプリケーションをマルチモジュール化する場合はまず最小限 の機能のみをもつトップの:appモジュールを分離することを心がける
  207. ご清聴ありがとうございました