Encore une fois sur la multi-modularité des applications Android

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



: , , , .





:



  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