Décomposer une application Android monolithique en modules n'est pas nouveau, et cette façon d'organiser le code est de plus en plus courante. Nous avons déjà abordé ce sujet lors de la réunion consacrée aux bonnes pratiques de travail avec des modules entre collègues. Nous avons recueilli cette expérience, l'avons testée sur notre projet et nous voulons partager les conclusions et les conseils auxquels nous sommes parvenus. Par conséquent, cet article peut être utile à la fois pour ceux qui ne pensent qu'à la division et pour ceux qui l'ont déjà commencé.
Les développeurs pensent généralement à utiliser la multi-modularité pour accélérer les temps de construction. Mais ce n'était pas la chose la plus importante pour nous. Outre la vitesse de construction, la multi-modularité fournit également une architecture plus stricte et la possibilité de réutiliser des fonctionnalités entre les projets.
, . , , . 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-. , .API
Impl
, , .
.
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
. , .