Modularyzacja Androida w 2026: Architektura Multi-Module i Pytania Rekrutacyjne
Przewodnik po modularyzacji aplikacji Android w 2026. Architektura wielomodułowa, Convention Plugins, Version Catalogs i pytania na rozmowy kwalifikacyjne dla developerów Kotlin.

Modularyzacja Androida przekształca monolityczne bazy kodu w systemy łatwe w utrzymaniu i skalowalne, gdzie zespoły pracują równolegle bez ingerencji w kod innych. Dobrze zmodularyzowana aplikacja Android kompiluje się szybciej, testuje w izolacji i pozwala nowym programistom wdrożyć się w ciągu dni, a nie tygodni.
Modularyzacja dzieli aplikację Android na niezależne moduły Gradle z wyraźnymi granicami. Niskie sprzężenie między modułami i wysoka spójność wewnątrz każdego z nich to główne zasady przewodnie. Referencyjna aplikacja Google Now in Android demonstruje ten wzorzec z ponad 40 modułami.
Dlaczego Architektura Multi-Module Ma Znaczenie dla Aplikacji Android
Jednomodułowa aplikacja Android rekompiluje wszystko przy zmianie jednej linii kodu. Modyfikacja warstwy sieciowej wywołuje rekompilację UI. Dwóch programistów edytujących różne funkcje tworzy konflikty merge w współdzielonych plikach. Czasy kompilacji rozciągają się do ponad 4 minut przy kompilacjach przyrostowych.
Architektura wielomodułowa bezpośrednio rozwiązuje te problemy:
- Szybsze buildy: Gradle rekompiluje tylko dotknięte moduły. Zmiana w
:feature:checkoutpozostawia:feature:profilenietknięty. - Równoległy rozwój: Zespoły są właścicielami oddzielnych modułów ze zdefiniowanymi API. Konflikty merge znacząco spadają.
- Izolowane testowanie: Testy jednostkowe działają na pojedynczym module bez ładowania całej aplikacji.
- Ponowne użycie kodu: Moduł
:core:uiz systemem projektowania staje się biblioteką użyteczną w wielu aplikacjach.
Oficjalny przewodnik modularyzacji Android zaleca strukturyzowanie modułów wokół funkcji lub warstw, które zmieniają się razem, zgodnie z zasadą pojedynczej odpowiedzialności.
Typy Modułów i Ich Odpowiedzialności
Repozytorium Now in Android ustanawia sprawdzoną taksonomię modułów, która skaluje się do aplikacji korporacyjnych.
include(":app")
// Core modules - shared utilities
include(":core:common")
include(":core:data")
include(":core:database")
include(":core:datastore")
include(":core:designsystem")
include(":core:domain")
include(":core:model")
include(":core:network")
include(":core:ui")
// Feature modules - screen-specific logic
include(":feature:home")
include(":feature:search")
include(":feature:bookmarks")
include(":feature:settings")Moduł app: Zawiera MainActivity, konfigurację nawigacji i szkielet na poziomie aplikacji. Zależy od wszystkich modułów funkcjonalnych i wymaganych modułów podstawowych.
Moduły core: Dostarczają współdzieloną funkcjonalność między funkcjami. :core:network obsługuje wywołania API. :core:database zarządza encjami Room. :core:designsystem definiuje komponenty wizualne aplikacji.
Moduły feature: Enkapsulują pojedynczy ekran lub przepływ użytkownika. Każdy moduł funkcjonalny zależy tylko od modułów podstawowych, których potrzebuje, nigdy od innych modułów funkcjonalnych.
Ta struktura wymusza jednokierunkowe zależności: funkcje zależą od core, a app zależy od funkcji.
Gradle Version Catalogs dla Zarządzania Zależnościami
Zarządzanie zależnościami w ponad 25 modułach bez konfliktów wersji wymaga scentralizowanej konfiguracji. Gradle Version Catalogs rozwiązują to za pomocą pojedynczego pliku libs.versions.toml.
# gradle/libs.versions.toml
[versions]
agp = "8.7.0"
kotlin = "2.1.0"
compose-bom = "2026.09.00"
ksp = "2.1.0-1.0.29"
hilt = "2.54"
room = "2.7.0"
retrofit = "2.11.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.15.0" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
[bundles]
compose = ["androidx-compose-ui", "androidx-compose-material3"]
room = ["room-runtime", "room-ktx"]
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }Moduły następnie odwołują się do zależności za pomocą type-safe accessors:
dependencies {
implementation(libs.bundles.compose)
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
}Gradle generuje te accessory podczas synchronizacji, umożliwiając autouzupełnianie IDE i walidację w czasie kompilacji.
Gotowy na rozmowy o Android?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Convention Plugins: Konfiguracja Build bez Powtórzeń
Projekt z 25 modułami bez convention plugins powtarza te same ustawienia compileSdk, minSdk, composeOptions i aplikacje pluginów w każdym build.gradle.kts. Różne moduły mogą przypadkowo używać różnych konfiguracji.
Convention plugins centralizują tę logikę w included build build-logic.
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.library")
pluginManager.apply("org.jetbrains.kotlin.android")
extensions.configure<LibraryExtension> {
compileSdk = 35
defaultConfig {
minSdk = 26
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
}
}
}Rejestracja pluginu w build-logic/convention/build.gradle.kts:
plugins {
`kotlin-dsl`
}
dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
}
gradlePlugin {
plugins {
register("androidLibrary") {
id = "myapp.android.library"
implementationClass = "AndroidLibraryConventionPlugin"
}
register("androidFeature") {
id = "myapp.android.feature"
implementationClass = "AndroidFeatureConventionPlugin"
}
register("androidHilt") {
id = "myapp.android.hilt"
implementationClass = "AndroidHiltConventionPlugin"
}
}
}Moduły feature wymagają teraz minimalnej konfiguracji:
plugins {
alias(libs.plugins.myapp.android.feature)
alias(libs.plugins.myapp.android.hilt)
}
dependencies {
implementation(projects.core.data)
implementation(projects.core.designsystem)
}Now in Android build-logic od Google używa dokładnie tego wzorca.
Wzorce Komunikacji Między Modułami
Moduły feature nie mogą bezpośrednio od siebie zależeć. To ograniczenie zachowuje równoległą kompilację i zapobiega cyklicznym zależnościom. Trzy wzorce obsługują komunikację między funkcjami.
Nawigacja przez Współdzielone Trasy
object Routes {
const val HOME = "home"
const val PRODUCT_DETAIL = "product/{productId}"
const val CHECKOUT = "checkout"
fun productDetail(productId: String) = "product/$productId"
}Moduły feature importują trasy z :core:navigation bez wiedzy o tym, który moduł implementuje każdą destynację.
Współdzielone Modele Domenowe
data class Product(
val id: String,
val name: String,
val price: BigDecimal,
val imageUrl: String
)Zarówno :feature:catalog jak i :feature:checkout zależą od :core:model dla klasy Product.
Event Bus lub Współdzielony ViewModel
Dla komunikacji w czasie wykonywania, współdzielony event bus w :core:common lub scoped ViewModel obsługuje zdarzenia między funkcjami:
object CartEventBus {
private val _events = MutableSharedFlow<CartEvent>()
val events: SharedFlow<CartEvent> = _events.asSharedFlow()
suspend fun emit(event: CartEvent) {
_events.emit(event)
}
}
sealed interface CartEvent {
data class ItemAdded(val productId: String) : CartEvent
data class ItemRemoved(val productId: String) : CartEvent
}Pytania Rekrutacyjne o Modularyzację Androida
Modularyzacja często pojawia się na rozmowach kwalifikacyjnych dla senior developerów Android. Rekruterzy oceniają zrozumienie systemów build, decyzji architektonicznych i praktycznych kompromisów.
Podczas omawiania modularyzacji na rozmowach kwalifikacyjnych warto odwoływać się do konkretnych metryk: poprawy czasów kompilacji, wzrostu wydajności zespołu lub konkretnej liczby modułów z projektów. Abstrakcyjne odpowiedzi o "lepszym rozdzieleniu odpowiedzialności" są mniej przekonujące niż "czasy kompilacji spadły z 4 minut do 45 sekund po modularyzacji."
P: Jaka jest różnica między zależnościami api i implementation w Gradle?
Zależności implementation są wewnętrzne dla modułu. Konsumenci modułu nie mają do nich dostępu. Zależności api są eksponowane tranzytywnie: jeśli moduł A używa api(libs.retrofit), moduł B zależący od A może używać klas Retrofit bezpośrednio.
Zasada: domyślnie używać implementation. Używać api tylko gdy zależność jest częścią publicznego API modułu. Nadużywanie api łamie enkapsulację i spowalnia buildy.
P: Jak obsługiwać nawigację między modułami feature, które nie zależą od siebie?
Należy stworzyć moduł :core:navigation zawierający definicje tras i interfejsy nawigacyjne. Moduły feature zależą od tego współdzielonego modułu. Moduł app łączy destynacje z implementacjami. To przestrzega zasady odwrócenia zależności: funkcje zależą od abstrakcji, nie od konkretnych implementacji.
P: Jakie są kompromisy przy zbyt dużej liczbie modułów?
Każdy moduł dodaje narzut konfiguracji Gradle: pliki build, przetwarzanie zasobów, łączenie manifestów. Czasy początkowej synchronizacji rosną. Punkt przełomowy różni się w zależności od projektu, ale 50+ modułów jest powszechne w dużych aplikacjach. Korzyści (równoległe buildy, izolowane testy, jasna odpowiedzialność) zazwyczaj przeważają narzut dla zespołów 5+ programistów.
P: Czym różnią się convention plugins od buildSrc?
Zmiany w buildSrc wywołują pełną rekompilację projektu. Convention plugins w included build rekompilują się tylko gdy zmienia się kod pluginu, nie gdy zmieniają się moduły konsumujące. To sprawia, że convention plugins są szybsze dla iteracyjnego rozwoju. Dodatkowo, convention plugins mogą być publikowane jako oddzielny artefakt do ponownego użycia w różnych repozytoriach.
P: Jak testować moduł feature w izolacji?
Moduły feature zależą od modułów core przez interfejsy. Należy tworzyć fake implementacje w test fixtures:
class FakeProductRepository : ProductRepository {
private val products = mutableListOf<Product>()
override suspend fun getProducts(): List<Product> = products
fun addProduct(product: Product) {
products.add(product)
}
}Testy modułów feature wstrzykują fake'i przez API testowe Hilt lub ręczne wstrzykiwanie przez konstruktor.
Więcej pytań praktycznych o architekturę Android znajduje się w module pytania rekrutacyjne o architekturę MVVM oraz moduł dependency injection.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Kluczowe Wnioski dla Projektów Multi-Module Android
- Strukturyzować moduły według funkcji (
:feature:home,:feature:checkout) i warstw (:core:data,:core:ui). Funkcje zależą od core, nigdy od siebie nawzajem. - Używać Gradle Version Catalogs (
libs.versions.toml) do centralizacji wersji zależności. Bundles grupują powiązane zależności dla czystszych plików build. - Implementować convention plugins w
build-logicaby wyeliminować zduplikowaną konfigurację między modułami. Jedna zmiana pluginu aktualizuje wszystkie moduły konsumujące. - Wymuszać jednokierunkowe zależności: app zależy od features, features zależą od core. Narzędzia jak Dependency Guard mogą automatyzować wymuszanie.
- Mierzyć wydajność build przed i po modularyzacji. Śledzić czasy kompilacji przyrostowej, nie tylko clean builds, aby zwalidować usprawnienia.
- Utrzymywać stabilne granice modułów. Częste restrukturyzacje negują korzyści z modularyzacji.
Znajdziesz błąd w Android?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 17 września 2026
Udostępnij
Powiązane artykuły

Android CameraX w 2026: Przechwytywanie Zdjęć i Wideo z Pytaniami Rekrutacyjnymi
Poradnik Android CameraX 1.6 obejmujący przypadki użycia Preview, ImageCapture i VideoCapture. Zawiera kod Kotlin, integrację z Compose oraz pytania rekrutacyjne.

Jetpack Navigation Compose w 2026: Nawigacja Type-Safe i Pytania Rekrutacyjne
Kompleksowy przewodnik po Jetpack Navigation Compose z nawigacją typu type-safe, zaawansowanymi wzorcami i pytaniami rekrutacyjnymi dla programistów Android.

Android WorkManager w 2026: Zadania w Tle, Ograniczenia i Pytania Rekrutacyjne
Kompleksowy przewodnik po WorkManager 2.11 w Kotlinie - od podstawowej konfiguracji przez ograniczenia i łańcuchy zadań po testowanie i pytania na rozmowy kwalifikacyjne.