- サーバーサイドの世界では、micro-servicesとか流行ってるね
- Androidの世界で、microな設計思想は取り入れられるのか?
Androidアプリのmoduleはどこまで分けられるのか?
- Domain
- Bridge
- Factory
- どのブリッジを使用するか選択する
- SQLite使う? Realm使う? そういう切り分けを行う
- Components
- Application
- 最終的な成果物作成モジュール
- google-services.jsonを組み込んだり、Crashlyticsを組み込んだり
- Flavorでapkを分けたりする
Componentsをどのように分けるか
Activityをinternal化する
- もちろん、関連するFragmentも全部internalにする
internal class SplashActivity : Activity() {
}
- Kotlinコードからアクセスできないだけで、AndroidManifestに書けばちゃんと動作する
Activity間の遷移をどうするか?
- [app]
- [app_components_splash]
- [app_components_main]
// app/build.gradle
// [app_components_splash]と[app_components_main]を完全に非依存としたい
dependencies {
implementation project(':app_components_splash')
implementation project(':app_components_main')
}
startActivity(Intent(context, MainActivity::class.java))
Component間に規約を設ける
- app_components_main/AndroidManifest.xmlにmeta-dataを追加
- こうすることで、Contextから拾うことができる
xml version="1.0" encoding="utf-8"
<manifest >
<application androidtheme="@style/Theme.AppCompat.NoActionBar">
<activity
androidname=".MainActivity">
<meta-data
androidname="com.eaglesakura.modules.COMPONENT_KEY"
androidvalue="com.eaglesakura.modules.screen.MAIN" />
</activity>
</application>
</manifest>
Contextからmeta-data経由でIntentを生成する
packageInfo = context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_META_DATA
)
for (activityInfo in (packageInfo.activities ?: emptyArray())) {
val componentKey = activityInfo.metaData?.getString("com.eaglesakura.modules.COMPONENT_KEY")
if("com.eaglesakura.modules.screen.MAIN" == componentKey) {
return Intent().also {
it.component = ComponentName(context.packageName, activityInfo.name)
}
}
}
突き詰めるとどうなるか
- 1画面1module、1service1module、1provider1module...というレベルまで細分化できる
- [app] モジュールは、最終的なビルドフレーバーを分けたりapplication-idを確定させる程度の存在になる
利点はあるのか?
- UIやシステムコンポーネントレベルでの依存性が無くなるため、moduleの交換が容易になる
- 1画面だけ何か新しいライブラリを入れてみよう、といったことが行える
- デザインの変更
- 特定moduleを捨てる決断が容易になる
- module間が粗結合になるため、担当者を分けやすい
- 実際に分けてみると、依存しているライブラリが「この画面のためだけに導入した」という感じになる
- 意図せず使ってしまって密結合になるケースがある
- そういったケースを防げる
欠点は何か?
- moduleがめっちゃ増える
- PCへの負荷が増える
- Gradleの
implementation
api
, Kotlinの internal
に対する知識が最低限必要になる
- 何がinternalなのか、ではない
何がpublicなのか
に注目して設計し、それ以外は全部privateかinternalにする
- メモリを食う
- CircleCIのデフォルトメモリ(4GB)だと稀によく落ちる場合がある
- res/*.xml が増える
CIのメモリ不足対処
- どうしてもmodule数が増えるとメモリ使用量も増える
- 予算や規模の都合でresource_classが変更できない場合
- compileとassemble/testを分ける
./gradlew --no-daemon :app_components_main:compileDebugSources
./gradlew --no-daemon :app_components_main:testDebug
結論として、この設計は使い物になるか?