Una vez más sobre la multimodularidad de las aplicaciones de Android

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- . - .



: , , , .





:



  1. App- — , Feature-.



  2. Feature- — , , -. , , - (, UI- , , UI). Feature- API Feature- Core-.

    Feature- API , API . internal , API, «» Feature-. , . API Impl , , .

    .



  3. Core- — , , Feature-. , . Core- . : module-injector.



  4. 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. , .






. , , . , , , .



, .




All Articles