Dividir una aplicación de Android monolítica en módulos no es algo nuevo, y esta forma de organizar el código se está volviendo más común. Ya hemos tocado este tema en la reunión dedicada a las mejores prácticas de trabajo con módulos entre colegas. Hemos recopilado esta experiencia, la hemos probado en nuestro proyecto y queremos compartir las conclusiones y los consejos a los que llegamos. Por tanto, este artículo puede ser útil tanto para quienes solo están pensando en la separación, como para quienes ya la han iniciado.
Los desarrolladores suelen pensar en utilizar la multimodularidad para acelerar los tiempos de construcción. Pero eso no fue lo más importante para nosotros. Además de la velocidad de construcción, la multimodularidad también proporciona una arquitectura más estricta y la capacidad de reutilizar funciones entre proyectos.
, . , , . 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
. , .