Android组件化框架实践

Android组件化框架实践

2023年6月28日发(作者:)

Android组件化框架实践前⾔之前考虑到后续可能会参与到⼩包项⽬的开发中,因此希望利⽤平时的时间形成2套框架,后续新项⽬⽴项后可⽴即投⼊使⽤,快速上线。本⽂说明的是⼀套基于jetpack+coroutines基础上形成的⼀套组件化框架,本⽂主要描述组件化项⽬的通⽤配置和关键技术点的解决⽅案。为什么要组件化⼤家知道,随着项⽬的业务逐渐发展,业务功能越来越多,参与的开发⼈员也可能变更,修改合并代码容易出现冲突,于是在出现了模块化的概念,即把同⼀个业务板块的内容单独抽取⼀个module去实现,做到代码隔离。但是模块化也会带来⼀些新的问题,不同的模块之间为了实现数据跳转,依赖关系错综复杂,代码耦合性很强。⽽且最致命的是,项⽬迭代到⼀定程度,由于项⽬本⾝⽐较⼤,全量编译耗时往往⾮常巨⼤,因此引⼊组件化。总结下来组件化有如下2个优点:1. 加快编译速度。在开发阶段,业务模块(module_xxx)可以作为application独⽴编译,编译速度明显加快;2. 明确开发⼈员彼此职责。明确开发⼈员维护模块,只需要管理和熟悉相应的module,彼此互不打扰,公共基础功能根据是否是业务相关,降级到Common层或core层,或单独封装成lib调⽤;3. 更合规。相⽐插件化的解决⽅案,组件化并没有引⼊动态加载Apk的功能,保证在google play等商店上线时不会出现合规性问题;项⽬架构图架构图以上架构图是⽬前我完成的项⽬中的模块依赖关系图,各个模块承载功能说明如下:1. App壳module中没有任何业务代码,只有全局Application对象的代码,和全局主题、logo等配置;2. Module_main、Module_sample、Module_A、Module_B为4个业务模块,后续可根据项⽬中真实的模块做修改;3. lib_download代表下载功能,是⼀个library,某个业务模块如需要直接依赖即可;4. lib_common作为项⽬业务模块的业务公共组件,包括所有和项⽬界⾯相关的组件或基类;5. lib_core代表所有与项⽬界⾯⽆关的组件和类库及资源,包括基类、图⽚加载、⽇志、⽹络请求、基础Util等6. lib_fileprovider代表fileprovider的公共定义处理,common库直接依赖即可;7. lib_webview代表webview页⾯的模块,由于业务模块基本都会跳转webview,所以直接common库去依赖;组件化配置1. 在项⽬中的ties中添加组件化控制开关#当前是否是组件化模块,false表⽰整体编译,true表⽰分模块编译isComponentMode=false在开发阶段,将此参数调整为true,即可单个模块编译打包,合并打包测试或发布时,将此参数修改为false2. 新增模块调试⼊⼝启动类在module_XXX等业务模块下新建Debug包,并新增DebugActivity和XXXApplication作为组件化状态下,模块调试的⼊⼝类和Application对象,如下debug3. 新增模块编译时的Manifest⽂件在module_XXX等业务模块下新建module_manifest⽂件夹,提供AndroidManifest⽂件,此Manifest⽂件包含上⾯的DebugActivity⽂件作为App启动⼊⼝,及其他当前模块的所有Activitymanifest4. 抽取module_main、module_A、module_B等业务模块的⽂件中公共内容,形成⼀个模块通⽤的⽂件,样例如下if (oolean(isComponentMode)) { apply plugin: 'ation'} else { apply plugin: 'y'}apply plugin: 'kotlin-android'apply plugin: 'kotlin-kapt'android { compileSdkVersion eSdkVersion buildToolsVersion oolsVersion defaultConfig { minSdkVersion Version targetSdkVersion SdkVersion versionCode nCode versionName nName multiDexEnabled true testInstrumentationRunner "dJUnitRunner" consumerProguardFiles "" kapt { arguments { arg("AROUTER_MODULE_NAME", e()) } } } sourceSets { main { s = ['libs'] if (oolean(isComponentMode)) { //模块化,作为独⽴App应⽤运⾏ e 'src/main/module_manifest/' } else { e 'src/main/' resources { //合并打包版本时,排除debug⽂件夹下所有⽂件 exclude '*/debug/*' } } } } } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile(''), '' } release { minifyEnabled false proguardFiles getDefaultProguardFile(''), '' } } compileOptions { sourceCompatibility N_1_8 targetCompatibility N_1_8 } kotlinOptions { jvmTarget = '1.8' } buildFeatures { viewBinding true }}注意点:此⽂件顶部的module类型配置,通过步骤⼀中的参数动态改变模块的module类型sourceSets中通过步骤⼀的参数动态获取AndroidManifest⽂件5. 然后在对应的module_xxx中的顶部添加此公共⽂件的依赖,样例如下:apply from: "../"android { defaultConfig { if (oolean(isComponentMode)) { //Module作为独⽴App应⽤运⾏,需配置包名 applicationId "_main" } }}dependencies { implementation project(path: ':lib_common') //ARouter注解处理器 kapt r_compiler}6. 壳⼯程依赖。设置在⾮组件化模式下,依赖各个业务模块合并编译 dependencies { implementation fileTree(dir: "libs", include: ["*.jar"])

if (!oolean(isComponentMode)) { //⾮组件化模式下,依赖各个模块 implementation project(path: ':module_main') implementation project(path: ':module_sample') implementation project(path: ':module_a') implementation project(path: ':module_b') }

}7. 页⾯跳转组件化的第⼀⽬标就是业务模块间解耦,不相互依赖,如模块A的的⼀个Activity需要跳转模块B的⼀个Activity,在没有相互依赖的情况下,我们采⽤阿⾥的路由解决⽅案-ARouter,ARouter的配置过程参照官⽅⽂档,需要注意的点是kapt相关的依赖配置需要在各个业务模块module去添加,代码参考如下:dependencies { implementation project(path: ':lib_common') //ARouter注解处理器 kapt r_compiler}defaultConfig { minSdkVersion Version targetSdkVersion SdkVersion versionCode nCode versionName nName multiDexEnabled true testInstrumentationRunner "dJUnitRunner" consumerProguardFiles "" kapt { arguments { arg("AROUTER_MODULE_NAME", e()) } } }然后在各个模块的Activity或Fragment上配置对应的path,则可实现跨模块跳转;⾄此,可在修改isComponentMode参数不同的状态下实现分模块组件化编译运⾏或合并编译运⾏!框架组件化技术点分析通过上⾯的步骤相信你可以很快实现⼀个组件化框架的通⽤配置,但是如果你进⼀步深⼊组件化的使⽤后,会出现如下⼏个问题:问题⼀:组件间数据传递组件间的数据传递⼜分为两种情况:1.页⾯主动跳转携带数据组件化项⽬在业务开发过程中会出现如下问题:module_A和module_B之间要进⾏跳转,要使⽤⼀个共同的model,此时需要把该model对象放在Base中,但是随着业务的不断迭代,各个模块跳转之间跳转的业务会越来越多,导致model对象在base中越来越多,这部分内容严格来说是⾪属于业务代码,因此在base模块和业务模块之间新增lib_common模块,统⼀管理Model实体类、图⽚资源、Strings等内容2.模块数据被动拉取例如:在module_main模块需要获取到module_user模块的⽤户信息去做显⽰。此场景使⽤ARouter的依赖注⼊进⾏处理1. 先在lib_common模块建⽴⼀个获取user信息的接⼝interface IUserProvider : IProvider { /** * 提供User模块的数据 * * @param s */ fun getUserData(): String}2. 然后在module_user模块实现@Route(path = _DATA_PROVIDER)class UserProvider : IUserProvider { override fun getUserData(): String { return "这个是User模块的数据" } override fun init(context: Context?) { }}3. 再module_main模块获取数据val iSampleProvider = tance() .build(_DATA_PROVIDER).navigation() as ISampleProviderLog.d("TAG值",pleData())问题⼆: Application⽣命周期处理Application作为程序启动的⼊⼝,其中的onCreate()⽅法经常做⼀些初始化相关⼯作,这些初始化的代码中,⼜包含2类,其中⼀类是所有模块都要⽤到的全局配置,如ARouter初始化、⽹络请求头配置、刷新样式、⽇志打印配置等。另外⼀类是某个模块特定的初始化配置。⽬前在组件化框架中有如下三种实现⽅式⽅式⼀:统⼀处理将全局配置代码统⼀全部放在Common层的Application的基类中,某个模块特定的初始化配置代码放在app壳module的Application配置优点:处理简单,逻辑清晰缺点:模块特定的初始化代码需要在其他module中调⽤,⽆法实现代码隔离⽅式⼆: 反射处理1. 在lib_common模块中定义ICommonApplicationinterface ICommonApplication { fun onCreate()}2. 定义业务module配置类object ModuleApplicationConfig { private const val MODULE_MAIN = "_plication" private const val MODULE_SAMPLE = "_Application" private const val MODULE_A = "_cation" private const val MODULE_B = "_cation" val modules = mutableListOf(MODULE_MAIN, MODULE_SAMPLE, MODULE_A, MODULE_B)

}3. 在每个业务模块中定义Application回调接收class MainApplication : ICommonApplication {

override fun onCreate() { Log.d("Application⽣命周期", "MainApplication执⾏onCreate!!") //todo 执⾏Main_module模块需要特定执⾏的代码

}

}4. 在CommonApplication类的OnCreate()⽅法中通过反射各个业务module的Application实例,然后执⾏相关初始化代码open class CommonApplication : BaseApplication() { override fun onCreate() { te() initComponent() } private fun initComponent() { h { val clazz = e(it) val instance = tance() as ICommonApplication te() } }}

优点:可实现代码隔离,逻辑清晰缺点:通过反射实现,有性能问题,新增模块是需要修改module配置类⽅式三:APT处理此⽅法引⼊⼀个新的AppLifeCycle插件,他可以⽆侵⼊的获取到Application的⽣命周期,具体使⽤步骤如下:1. lib_common模块依赖applifecycle-api依赖//AppLifecycleapi 'd-AppLifecycleMgr:applifecycle-api:1.0.4'2. 各个业务模块module_xxx添加applifecycle-compiler注解处理器依赖(注意:java请⽤annotationProcessor关键字)kapt 'd-AppLifecycleMgr:applifecycle-compiler:1.0.4'3. 新建各个业务模块的Application,⽤于接受主Application的⽣命周期事件分发,以module_main模块的MainApplication为例:@AppLifecycleclass MainApplication : IApplicationLifecycleCallbacks { /** * 设置优先级 * * @return */ override fun getPriority(): Int { return NORM_PRIORITY } override fun onCreate(context: Context?) { Log.d("Application⽣命周期", "MainApplication执⾏onCreate!!") } override fun onTerminate() { } override fun onLowMemory() { } override fun onTrimMemory(level: Int) { }}4. 壳⼯程添加AppLifecycle插件配置,并注册⽣命周期事件项⽬修改如下:buildscript { _version = "1.5.21" repositories { google() mavenCentral() //applifecycle插件仓也是jitpack maven { url '' } } dependencies { classpath ':gradle:7.0.3' classpath ':kotlin-gradle-plugin:1.5.31' //加载插件applifecycle classpath 'd-AppLifecycleMgr:applifecycle-plugin:1.0.3' }}app模块修改如下://使⽤插件applifecycleapply plugin: 'cle'优点:实现代码隔离,通过APT插件模式处理,编译阶段完成引⽤,⽆性能问题缺点:代码逻辑调⽤⽆直接关联,要引⼊三⽅库结论:⽬前项⽬经过评估引⼊⽅式三处理其他⽬前还在持续不断完善中,⼒求可以覆盖更多场景,实现最终快速实现业务,⾼质量交付的⽬的!参考资料:

发布者:admin,转转请注明出处:http://www.yc00.com/news/1687956020a60674.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信