Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

山本純平 twitter: @boohbah github: yamamotoj

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

compile指定 (before 3.0)

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

implementation指定 (after3.0)

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

api指定 (after3.0)

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

ビルド時間の計測実験

Slide 47

Slide 47 text

シングルモジュール 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() } }

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

ビルド条件 • 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

Slide 53

Slide 53 text

フルビルドで比較

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

シングルモジュールのビルド結果 :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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

直列マルチモジュールのビルド結果 :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

Slide 69

Slide 69 text

直列マルチモジュールのビルド結果 :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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

インクリメンタルビルド • それぞれのプロジェクトでソースコードを一行だけ変更し て、ビルド時間を比較する • 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 が動作するように

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

並列マルチモジュールのビルド :app :module1 :module2 :module3 :module4 :app
 1.9s :module1 4.3s :appモジュールの
 Annotation Processingが再実行

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text

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

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

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

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

Eightの各機能の依存関係 :component_ main :component_ post_detail :component_ person_detail :component_ create_post :component_ chat :component_ my_page :component_ camera :component_ search :component_ on_boarding

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

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

Slide 183

Slide 183 text

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

Slide 184

Slide 184 text

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

Slide 185

Slide 185 text

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

Slide 186

Slide 186 text

詳しくは…

Slide 187

Slide 187 text

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

Slide 188

Slide 188 text

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

Slide 189

Slide 189 text

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

Slide 190

Slide 190 text

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

Slide 191

Slide 191 text

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

Slide 192

Slide 192 text

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

Slide 193

Slide 193 text

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

Slide 194

Slide 194 text

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

Slide 195

Slide 195 text

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

Slide 196

Slide 196 text

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

Slide 197

Slide 197 text

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

Slide 198

Slide 198 text

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

Slide 199

Slide 199 text

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

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

分割した機能の連携を解決するために • 連携の解決をするための上位モジュールが必要 :component_ main :component_ post_detail :component_ person_detail :component_ create_post :component_ chat :component_ my_page :component_ camera :component_ search :component_ on_boarding :app

Slide 202

Slide 202 text

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

Slide 203

Slide 203 text

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

Slide 204

Slide 204 text

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

Slide 205

Slide 205 text

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

Slide 206

Slide 206 text

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

Slide 207

Slide 207 text

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