Das Aufteilen einer monolithischen Android-Anwendung in Module ist nicht neu, und diese Art der Code-Organisation wird immer häufiger. Wir haben dieses Thema bereits auf dem Treffen angesprochen, das sich mit den Best Practices für die Arbeit mit Modulen unter Kollegen befasst. Wir haben diese Erfahrung gesammelt, sie in unserem Projekt getestet und möchten die Schlussfolgerungen und Ratschläge teilen, zu denen wir gekommen sind. Daher kann dieser Artikel sowohl für diejenigen nützlich sein, die nur über die Aufteilung nachdenken, als auch für diejenigen, die bereits damit begonnen haben.

Entwickler denken normalerweise darüber nach, Multimodularität zu verwenden, um die Erstellungszeiten zu verkürzen. Das war aber nicht das Wichtigste für uns. Neben der Erstellungsgeschwindigkeit bietet die Multimodularität auch eine strengere Architektur und die Möglichkeit, Features zwischen Projekten wiederzuverwenden.
, . , , . Gradle - , Buck Bazel. , 300 .
. . - Android Wear. , .
, Java, , internal. : api + impl. , . .
, , Dagger, . , , . Kotlin, — , .
, , .
-, . , . . , AppComponent, ( KAPT) . , — , Gradle Android Gradle Plugin, , .
-, . . . , . Kotlin Multiplatform . , .
-, . . , , . , .
( , ) — . Maven-. , .
Git- . - .
: , , , .
:
App- — , Feature-.
Feature- — , , -. , , - (, UI- , , UI). Feature- API Feature- Core-.
Feature- API , API . internal , API, «» Feature-. , .APIImpl, , .
.
Core- — , , Feature-. , . Core- . : module-injector.
Module-injector — , . , . .
— API Impl Feature-, Feature- . internal- Kotlin.
Example- , App-. ( ) , . .
:

Module-Injector
, . , . , , . , , Dagger ( DI-). , :
interface ComponentHolder<C : BaseAPI, D : BaseDependencies> {
fun init(dependencies: D)
fun get(): C
fun reset()
}
interface BaseDependencies
interface BaseAPI
. Feature- . ( , ) internal, .
- , , , Kotlin ( 2020-) module-injector. . .
, , — . : , . UI, , — .
Feature- , - , Core-. , :

, API Feature-. Feature-: :feature_purchase_api :feature_purchase_impl. API- , :module-injector. API-.
, . , .
ComponentHolder-:
object PurchaseComponentHolder : ComponentHolder<PurchaseFeatureApi, PurchaseFeatureDependencies> {
private var purchaseComponentHolder: PurchaseComponent? = null
override fun init(dependencies: PurchaseFeatureDependencies) {
if (purchaseComponentHolder == null) {
synchronized(PurchaseComponentHolder::class.java) {
if (purchaseComponentHolder == null) {
purchaseComponentHolder = PurchaseComponent.initAndGet(dependencies)
}
}
}
}
override fun get(): PurchaseFeatureApi {
checkNotNull(purchaseComponentHolder) { "PurchaseComponent was not initialized!" }
return purchaseComponentHolder!!
}
override fun reset() {
purchaseComponentHolder = null
}
}
:
- .
-
init(), . -
get(), API . -
reset(), , .
. , .
PurchaseFeatureApi PurchaseFeatureDependencies , .
ComponentHolder Dagger-:
@Component(dependencies = [PurchaseFeatureDependencies::class], modules = [PurchaseModule::class])
@PerFeature
internal abstract class PurchaseComponent : PurchaseFeatureApi {
companion object {
fun initAndGet(purchaseFeatureDependencies: PurchaseFeatureDependencies): PurchaseComponent {
return DaggerPurchaseComponent.builder()
.purchaseFeatureDependencies(purchaseFeatureDependencies)
.build()
}
}
}
initAndGet(), ComponentHolder. , Dagger . DI- .
, app :
@Module
class AppModule {
@Singleton
@Provides
fun provideScannerFeatureDependencies(featurePurchase: PurchaseFeatureApi): ScannerFeatureDependencies {
return object : ScannerFeatureDependencies {
override fun dbClient(): DbClient = CoreDbComponent.get().dbClient()
override fun httpClient(): HttpClient = CoreNetworkComponent.get().httpClient()
override fun someUtils(): SomeUtils = CoreUtilsComponent.get().someUtils()
override fun purchaseInteractor(): PurchaseInteractor = featurePurchase.purchaseInteractor()
}
}
// -
@Provides
fun provideFeatureScanner(dependencies: ScannerFeatureDependencies): ScannerFeatureApi {
ScannerFeatureComponentHolder.init(dependencies)
return ScannerFeatureComponentHolder.get()
}
...
}
provideScannerFeatureDependencies() ScannerFeatureDependencies, provideFeatureScanner() ComponentHolder- .
, app- . , . app- , . app- .
, .
, ComponentHolder reset(), . UI, reset() Lifecycle Observer- ( Activity, ):
public override fun onPause() {
super.onPause()
...
if (isFinishing) {
AntitheftFeatureComponentHolder.reset()
}
}
, , . get().
// get() Feature-:
object PurchaseComponentHolder : ComponentHolder<PurchaseFeatureApi, PurchaseFeatureDependencies> {
private var purchaseComponentHolder: PurchaseComponent? = null
...
override fun get(): PurchaseFeatureApi {
checkNotNull(purchaseComponentHolder) { "PurchaseComponent was not initialized!" }
return purchaseComponentHolder!!
}
override fun reset() {
purchaseComponentHolder = null
}
// get() app-:
// @Singleton Provider, , get() Dagger-
@Provides
fun provideFeatureScanner(dependencies: ScannerFeatureDependencies): ScannerFeatureApi {
ScannerFeatureComponentHolder.init(dependencies)
return ScannerFeatureComponentHolder.get()
}
}
, DI- app- (, Singleton Dagger). init() , , reset() .
API- — Provider<T> :
class GlobalNavigator @Inject constructor(
// Provider get()
private val featureScanner: Provider<ScannerFeatureApi>,
private val featureAntitheft: Provider<AntitheftFeatureApi>,
private val context: Context
) : Navigator {
...
featureScanner.get().scannerStarter().start(context) //
...
}
UI
, API , . API , UI-, Activity, , View.
Activity API :
interface AntitheftStarter {
fun start(context: Context)
}
Activity , Intent:
internal class AntitheftStarterImpl @Inject constructor() : AntitheftStarter {
override fun start(context: Context) {
val intent = Intent(context, AntitheftActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}
FragmentManager , API , . : Cicerone, Navigation Component FragmentManager.
, , . , .
Core-ui
UI- , , UI- . UIKit. , Application.
, theme.xml, (, Example-, , ). core-ui , , , . UIKit (api implementation) Feature-, UI. .
Core-strings
, , .
, , , : . . , . .
Core-native
C++-. JNI-, Java-. , SDK. , , ABI. , .