Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Sansan
February 08, 2019

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

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

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

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

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

Sansan

February 08, 2019
Tweet

More Decks by Sansan

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. モジュールの種類
    Application Module
    APKとしてリリース可能
    Library Module
    AARとしてリリース可能
    Dynamic Feature Module
    インストール後に追加できる

    View Slide

  9. なぜマルチモジュールにするのか?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  13. 複数のアプリケーションの管理を行える
    product avorによる複数アプリケーションの管理
    flavorDimensions "sourceSet"
    productFlavors {
    demo {
    dimension "sourceSet"
    }
    production {
    dimension "sourceSet"
    }
    }
    • build variant
    をdemoにする
    と、production
    のソースは管理
    外になってしまう
    • リファクタリング
    が効かない

    View Slide

  14. 複数のアプリケーションの管理を行える
    • すべてのコードでリファクタリングが適用される
    :app_demo
    :app_production
    :library_demo
    :library_production
    :library_common

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  18. まとめ
    • ビルドを高速化
    • モジュールごとにテストを実行できる
    • モジュールを使って複数アプリケーションの管理を行える
    • ソースコードの依存関係を強制できる
    • Kotlinではモジュールでのコードの可視性を定義できる
    • Dynamic Feature Moduleでインストール時の容量を
    削減できる

    View Slide

  19. マルチモジュールによる
    ビルドの高速化

    View Slide

  20. マルチモジュールによるビルドの高速化
    • Android Gradle Plugin3.0以降の動作のしくみ
    • ビルド時間の計測実験
    • pluginやannotation processorがある場合の高速
    化テクニック
    • モジュール分割によって効果的に高速化する方法

    View Slide

  21. Android Gradle Plugin 3.0
    以降の動作の仕組み

    View Slide

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

    View Slide

  23. compile指定
    (before 3.0)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. implementation指定
    (after3.0)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. api指定
    (after3.0)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. 挙動の違い
    compile(before3.0)
    api(after3.0)
    implementation
    (after3.0)
    モジュール
    の参照
    間接的に依存しているすべ
    てのモジュールも参照できる
    直接依存しているモジュールの
    みが参照可能
    ビルドの
    伝播
    間接的に依存するすべての
    モジュールのコンパイルが実
    行される
    直接依存するモジュールのみが
    コンパイルされる

    View Slide

  45. Android Gradle Pluginまとめ
    • 依存関係の指定を”compile”から”implementation”
    にすることで無駄な再コンパイルを抑えることができる
    • “compile”と同等の挙動で使いたければ”api”指定を
    使う

    View Slide

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

    View Slide

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

    View Slide

  48. モジュール構成の違う3種類のアプリを作っ
    てビルドにかかる時間を比較
    • フルビルド
    • インクリメンタルビルド

    View Slide

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

    View Slide

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

    • 各モジュールは直列でそれぞれが次
    のモジュールに依存する
    :app
    :module4
    :module3
    :module2
    :module1

    View Slide

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

    View Slide

  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

    View Slide

  53. フルビルドで比較

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  57. ビルド時に何が起こったかを確認してみる
    • gradle build scan
    • build結果をgradleのサーバに送信し解析
    • 送信先は scans.gradle.org
    • build時に —scan オプションを追加し、利用規約に
    同意することで利用可能
    ./gradlew assembleDebug —no-build-cache —scan

    View Slide

  58. View Slide

  59. View Slide

  60. シングルモジュールのビルド結果
    36s
    https://scans.gradle.com/s/5v3vnsh4lmepg

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  65. 直列マルチモジュールのビルド結果
    39s
    https://scans.gradle.com/s/o4pvtp3dx5nvi

    View Slide

  66. 直列マルチモジュールのビルド結果
    :module1
    kapt stub作成

    Annotation
    Processing

    Kotlinコンパイル
    9.3s
    39s
    https://scans.gradle.com/s/o4pvtp3dx5nvi

    View Slide

  67. 直列マルチモジュールのビルド結果
    :module1
    kapt stub作成

    Annotation
    Processing

    Kotlinコンパイル
    9.3s
    :module2
    kapt stub作成

    Annotation
    Processing

    Kotlinコンパイル
    7.0s
    39s
    https://scans.gradle.com/s/o4pvtp3dx5nvi

    View Slide

  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

    View Slide

  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

    View Slide

  70. 並列マルチモジュールの結果
    31s
    https://scans.gradle.com/s/dhvaghy7in2vq

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  74. シングル vs 並列
    シングルモジュールのビルド
    並列マルチモジュールのビルド
    36s
    31s

    View Slide

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

    View Slide

  76. フルビルドの結果
    • モジュール分割により、コンパイルやAnnotation
    Processingの順序は大きく変わる
    • コンパイルやAnnotation Processingを並列に実行
    することでビルド速度は若干向上
    • シングルモジュールでもコンパイルやAnnotation
    Processingの並列処理はなされており、マルチモジュー
    ルで並列度を上げたところで劇的に速度が向上するわけ
    ではない

    View Slide

  77. インクリメンタルビルドで比較

    View Slide

  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
    が動作するように

    View Slide

  79. 変更箇所: シングルモジュール
    • :appモジュールのコードを一行
    変更
    :app
    MainActivity
    2000class

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  87. 直列マルチモジュールのビルド
    10s
    https://scans.gradle.com/s/v2xy6oultdawm

    View Slide

  88. 直列マルチモジュールのビルド
    10s
    https://scans.gradle.com/s/v2xy6oultdawm
    :module1

    kapt stubの作成

    Annotation
    processing
    3.7s

    View Slide

  89. 直列マルチモジュールのビルド
    10s
    https://scans.gradle.com/s/v2xy6oultdawm
    :module2

    kapt stubの作成

    Annotation
    processing
    3.7s
    :module1

    kapt stubの作成

    Annotation
    processing
    3.7s

    View Slide

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

    3.7s
    :module2

    3.7s

    View Slide

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

    View Slide

  92. 並列マルチモジュールのビルド
    :module1

    kapt stubの作成

    Annotation processing
    4.3s
    7.4s

    View Slide

  93. 並列マルチモジュールのビルド
    :module1

    kapt stubの作成

    Annotation processing
    4.3s
    :app

    Annotation
    processing
    1.2s
    7.4s

    View Slide

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

    1.9s
    :module1 4.3s
    :appモジュールの

    Annotation
    Processingが再実行

    View Slide

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

    View Slide

  96. Google I/O 2017でのお話
    • Google I/O 2017 speeding up your build
    • https://www.youtube.com/watch?v=7ll-rkLCtyk

    View Slide

  97. Google I/O 2017でのお話
    • Javacはインクリメンタルだが
    Annotation processingはイン
    クリメンタルなビルドには対応して
    いない
    • Annotation processingを使っ
    ている場合は一つのクラスを変更し
    ただけでも全てを再コンパイルしな
    ければならない
    :app

    View Slide

  98. Google I/O 2017でのお話
    • 小さなモジュールに分割することで
    Annotation processingの実行
    範囲を最小限におさえることができ

    :app
    :module1
    :module2

    View Slide

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

    View Slide

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

    View Slide

  101. 特殊なpluginがある場合の高速
    化テクニック

    View Slide

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

    View Slide

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

    View Slide

  104. Realmのオブジェクト定義
    public class Article extends RealmObject {
    @PrimaryKey
    public String id;
    public String content;
    }
    JAVA
    永続化されるデータを
    Javaのフィールドとし
    て定義

    View Slide

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

    View Slide

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

    View Slide

  107. RealmObjectのデータ取得
    Article article = realm.where(Article.class)
    .equals(“id”, “1”)
    .findFirst();
    String content = article.content;
    JAVA
    フィールドにアクセスされた時点
    でcontentのデータが遅延ロー
    ドされる

    View Slide

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

    View Slide

  109. なぜこんなことが可能か?
    public class Article extends ReamObject {
    @PrimaryKey
    public String id;
    public String content;
    public String realmGet$content() {
    return row.getString(2);
    }
    /* ... */
    }
    JAVA
    コードの追加

    View Slide

  110. なぜこんなことが可能か?
    String content = article.content;
    JAVA

    View Slide

  111. なぜこんなことが可能か?
    String content = article.content;
    JAVA
    String content = article.realmGet$content();
    JAVA
    呼び出し元のコードを書き換え

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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が実行され

    View Slide

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

    View Slide

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

    View Slide

  119. モジュールの切り出しで高速化

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  128. Bytecode weavingが適用される範囲
    :app
    :app
    :realm
    before after

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  134. ビルドが高速化しない構造

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  151. 水平方向のモジュール分割

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  155. ドメイン層
    • ビジネスルールに関するロ
    ジック
    • Androidに依存しない純粋
    なJava(Kotlin)のモジュー

    • 他のレイヤと接続するために
    はインターフェイスを用いる
    プレゼンテーション層
    ドメイン層
    データ層

    View Slide

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

    View Slide

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

    View Slide

  158. レイヤ構造をモジュールで表現
    :app
    :domain
    :data

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  165. 垂直方向のモジュール分割

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  177. app:モジュールにて実装クラスを定義
    :component_
    post_detail
    :component_
    person_detail
    class IntentResolverImpl: IntentResolver {
    override fun getPersonDetailActivityIntent():
    Intent = Intent(…)
    override fun getPostDetailActivityIntent():
    Intent = Intent(…)
    }
    app:
    お互いのモジュールを参照

    View Slide

  178. DIにて解決
    :component_
    post_detail
    :component_
    person_detail
    app:
    :common
    interface IntentResolver{}
    class IntentResolverImpl: IntentResolver
    inject inject

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  183. 既存のアプリをマルチモジュール
    化する

    View Slide

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

    View Slide

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

    View Slide

  186. 詳しくは…

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  190. :appモジュールの主な機能
    • Applicationクラス
    • サブモジュール間の依存関係
    を解決する
    • ドメイン層を介したプレゼ
    ンテーション層とデータ層
    の実装の解決
    • 各機能間の連携を抽象化し
    て解決
    • サブモジュールのビルドが速
    くなるようにできるだけ軽く
    :legacy
    :app

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  202. :appモジュールを分離したあとは?
    :legacy
    :app

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  206. まとめ
    • Annotation processingが存在する場合、モジュール間の依存関
    係を適切に設定することによってマルチモジュール化によってビルド
    を高速化することができる
    • Incremental annotation processingの実装によってマルチ
    モジュールはビルドの高速化に対してあまり意味を持たなくなるか

    • 水平方向、垂直方向にモジュールを分割することによってレイヤの依
    存関係を強制し、関心事を分離することができる
    • 既存のアプリケーションをマルチモジュール化する場合はまず最小限
    の機能のみをもつトップの:appモジュールを分離することを心がける

    View Slide

  207. ご清聴ありがとうございました

    View Slide