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. 複数のアプリケーションの管理を行える product avorによる複数アプリケーションの管理 flavorDimensions "sourceSet" productFlavors { demo { dimension

    "sourceSet" } production { dimension "sourceSet" } } • build variant をdemoにする と、production のソースは管理 外になってしまう • リファクタリング が効かない
  2. compile指定 (before3.0) dependencies{ compile: module1 } dependencies{ compile: module2 }

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

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

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

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

    みが参照可能 ビルドの 伝播 間接的に依存するすべての モジュールのコンパイルが実 行される 直接依存するモジュールのみが コンパイルされる
  7. シングルモジュール 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() } }
  8. ビルド条件 • 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
  9. ビルド時に何が起こったかを確認してみる • gradle build scan • build結果をgradleのサーバに送信し解析 • 送信先は scans.gradle.org

    • build時に —scan オプションを追加し、利用規約に 同意することで利用可能 ./gradlew assembleDebug —no-build-cache —scan
  10. 直列マルチモジュールのビルド結果 :module1 kapt stub作成
 Annotation Processing
 Kotlinコンパイル 9.3s :module2 kapt

    stub作成
 Annotation Processing
 Kotlinコンパイル 7.0s 39s https://scans.gradle.com/s/o4pvtp3dx5nvi
  11. 直列マルチモジュールのビルド結果 :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
  12. 直列マルチモジュールのビルド結果 :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
  13. インクリメンタルビルド • それぞれのプロジェクトでソースコードを一行だけ変更し て、ビルド時間を比較する • 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 が動作するように
  14. Google I/O 2017でのお話 • Google I/O 2017 speeding up your

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

    Annotation processingを使っ ている場合は一つのクラスを変更し ただけでも全てを再コンパイルしな ければならない :app
  16. Realmのオブジェクト定義 public class Article extends RealmObject { @PrimaryKey public String

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

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

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

    id; public String content; public String realmGet$content() { return row.getString(2); } /* ... */ } JAVA コードの追加
  20. 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
  21. 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が実行され る
  22. 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
  23. 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
  24. 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
  25. 再掲: インクリメンタルビルドの結果 • 変更されたモジュールとそれを直接参照しているモジュー ルのみのAnnotation processingとコンパイルが実 行される • Annotation processingは実行時に影響するモジュー

    ル全体で再実行されるため、影響するもモジュールが小 さいほうがビルドが速い モジュールの依存関係の構造によって ビルドの速度が変わってくる
  26. app:モジュールにて実装クラスを定義 :component_ post_detail :component_ person_detail class IntentResolverImpl: IntentResolver { override

    fun getPersonDetailActivityIntent(): Intent = Intent(…) override fun getPostDetailActivityIntent(): Intent = Intent(…) } app: お互いのモジュールを参照
  27. 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
  28. :appモジュールの主な機能 • Applicationクラス • サブモジュール間の依存関係 を解決する • ドメイン層を介したプレゼ ンテーション層とデータ層 の実装の解決

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

    (application as MyApplication) .component .mainActivityComponentBuilder() .build() .inject(this) } } Application classΛࢀর͠ ͍ͯΔ
  30. まとめ • Annotation processingが存在する場合、モジュール間の依存関 係を適切に設定することによってマルチモジュール化によってビルド を高速化することができる • Incremental annotation processingの実装によってマルチ

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