组件化思想
组件化对于各个领域都已经不是新鲜的词汇了,在Android上组件化已经遍地开花了,这里只是记录一下我对组件化的理解,有不对的地方请大家指出。
组件化是为了解决什么问题
优秀的工程项目应该满足高内聚低耦合思想,各个功能有明显的边界划分,各个模块各司其职,至少在修改的时候不是牵一发而动全身,其他人在接手的时候也能快速理解。
如果你的项目存在一下问题,可以考虑使用组件化了:
代码耦合严重,eventbus满天飞
依赖严重,编译慢
功能模块界限不清晰
多人开发的时候经常发生合并冲突
组件化的存在就是为了解决上述问题,将功能相关的作为一个单独模块,将经常使用的基础功能抽离出来作为基础模块,多人开发时每个人负责独立模块。
理想状态下,每个模块能进行独立开发、独立测试,在打包时合并为一个项目。
怎样使用组件化
大致分为以下步骤:
解决依赖问题
抽离公用模块
创建独立功能模块
模块间通信
我们的目的是搭建MVVM模式下的组件化框架,关于组件化的理论知识我们不做过多的介绍了。
参考:
https://www.jianshu.com/p/5e20a674062f
https://juejin.cn/post/6881116198889586701
搭建
解决依赖问题
组件化分为宿主App+多module,如果每个moduel都使用自己的依赖,那么管理起来相当费劲,不同module之间依赖的版本不同、升级都是问题,所以我们首先寻求的就是统一依赖管理
方式一:
在根目录下创建config.gradle文件,这个文件就是管理常用配置和依赖:
ext{ versions = [ compileSdkVersion : 29, buildToolsVersion : "29.0.2", minSdkVersion : 21, targetSdkVersion : 29, versionCode : 1, versionName : "1.0" ] // 组件化与集成化切换时,设置不同的applicationId appId = [ app : "com.example.modular.todo", shop : "com.example.modular.shop" ] dependencies = [ appcompat : "androidx.appcompat:appcompat:${appcompatVersion}", constraintlayout : "androidx.constraintlayout:constraintlayout:${constraintlayoutVersion}", ] //这里只做实例 }
然后再根目录的build.gradle中添加:
apply from : 'config.gradle'
在不同的module模块中的引用:
def supports = rootProject.ext.dependencies dependencies { implementation fileTree (dir: 'libs' , include : ['*.jar' ]) implementation supports.appcompat implementation supports.constraintlayout supports.each { key, value -> implementation value } }
还有一种buildSrc 管理,我并不常用这种方式,更习惯下面这种:
最终实现方式
1. 新建moduel模块,名称为version
2. 将version模块的build.gradle修改为如下:
buildscript { ext.kotlin_version = "1.5.20" repositories { google () mavenCentral () } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin : 'kotlin' apply plugin : 'java-gradle-plugin' repositories { google () mavenCentral () } dependencies { implementation gradleApi () implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } compileKotlin { kotlinOptions { jvmTarget = 1.8 } } compileTestKotlin { kotlinOptions { jvmTarget = 1.8 } } gradlePlugin { plugins { version { id = 'com.xlu.version' implementationClass = 'com.xlu.version.VersionPlugin' } } }
在此模块下创建VersionPlugin
类,如下:
import org.gradle.api.Pluginimport org.gradle.api.Projectclass VersionPlugin : Plugin <Project > { override fun apply (target: Project ) { } }
3. 重要的一步:
打开工程根目录下的setting.gradle
文件
删除原来的include ':version'
修改为includeBuild("version")
4. 在version模块下创建Dependency类,管理依赖
当然这个类名称自己随便起,实例如下:
object Dependency { object ProjectConfig { const val compileSdkVersion = 30 const val buildToolsVersion = "30.0.1" const val applicationId = "com.example.mvvm_develop" const val minSdkVersion = 16 const val targetSdkVersion = 30 const val versionCode = 1 const val versionName = "1.0" const val isAppMode = false } object Version{ const val Junit = "4.13" const val Material = "1.2.0" const val AppCompat = "1.2.0" const val CoreKtx = "1.3.1" const val ConstraintLayout = "2.0.1" const val TestExtJunit = "1.1.2" const val TestEspresso = "3.3.0" const val ActivityKtx = "1.1.0" const val FragmentKtx = "1.2.5" const val MultiDex = "2.0.1" const val Kotlin = "1.5.10" const val Coroutines = "1.5.0" const val Lifecycle = "2.3.1" const val Hilt = "2.35.1" const val HiltAndroidx = "1.0.0" } object DependencyImp{ const val Junit = "junit:junit:${Version.Junit} " const val Material = "com.google.android.material:material:${Version.Material} " const val AndroidJUnitRunner = "androidx.test.runner.AndroidJUnitRunner" const val AppCompat = "androidx.appcompat:appcompat:${Version.AppCompat} " const val CoreKtx = "androidx.core:core-ktx:${Version.CoreKtx} " const val ConstraintLayout = "androidx.constraintlayout:constraintlayout:${Version.ConstraintLayout} " const val TestExtJunit = "androidx.test.ext:junit:${Version.TestExtJunit} " const val TestEspresso = "androidx.test.espresso:espresso-core:${Version.TestEspresso} " const val ActivityKtx = "androidx.activity:activity-ktx:${Version.ActivityKtx} " const val FragmentKtx = "androidx.fragment:fragment-ktx:${Version.FragmentKtx} " const val MultiDex = "androidx.multidex:multidex:${Version.MultiDex} " const val Kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${Version.Kotlin} " const val CoroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.Coroutines} " const val CoroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.Coroutines} " const val ViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.Lifecycle} " const val ViewModelSavedState = "androidx.lifecycle:lifecycle-viewmodel-savedstate:${Version.Lifecycle} " const val LiveData = "androidx.lifecycle:lifecycle-livedata-ktx:${Version.Lifecycle} " const val Lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:${Version.Lifecycle} " const val LifecycleCompilerAPT = "androidx.lifecycle:lifecycle-compiler:${Version.Lifecycle} " const val HiltCore = "com.google.dagger:hilt-android:${Version.Hilt} " const val HiltApt = "com.google.dagger:hilt-compiler:${Version.Hilt} " const val HiltAndroidx = "androidx.hilt:hilt-compiler:${Version.HiltAndroidx} " } }
上面代码应该很好理解吧,我们在上述创建好我们常用的依赖之后,就可以在其他modue中引用了。
上面类中的依赖在编译时并不会创建,只有在module中引用才会创建。
5. 其它module中使用
我们以宿主app模块为例,打开app模块的build.gradle文件
// setup1import com.example.plugin.Dependency // setup2plugins { id 'com.android.application' id 'kotlin-android' id 'com.example.plugin' // 注意这里哦,不然上述的import会报错 }
一些配置基础:
android { compileSdkVersion Dependency .ProjectConfig . compileSdkVersion buildToolsVersion Dependency .ProjectConfig . buildToolsVersion defaultConfig { applicationId Dependency .ProjectConfig . applicationId minSdkVersion Dependency .ProjectConfig . minSdkVersion targetSdkVersion Dependency .ProjectConfig . targetSdkVersion versionCode Dependency .ProjectConfig . versionCode versionName Dependency .ProjectConfig . versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } ...... }
依赖管理:
dependencies { testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' api Dependency.DependencyImp .AppCompat api Dependency.DependencyImp .Material api Dependency.DependencyImp .AndroidXLeagcy api Dependency.DependencyImp .ActivityKtx api Dependency.DependencyImp .FragmentKtx api Dependency.DependencyImp .MultiDex api Dependency.DependencyImp .Kotlin api Dependency.DependencyImp .CoreKtx api Dependency.DependencyImp .CoroutinesCore api Dependency.DependencyImp .CoroutinesAndroid api Dependency.DependencyImp .NavigationFragment api Dependency.DependencyImp .NavigationUI api Dependency.DependencyImp .Lifecycle api Dependency.DependencyImp .Livedata api Dependency.DependencyImp .Viewmodel }
具体参考Demo
上面可以看到我们用Dependency.ProjectConfig.xx这种方式代替了原来的数据,在Dependency类中统一管理
注意version所在的位置应该与Project处于同一层级,而不是与app module处于同一层级:
不然MakeProjectk可能会报错:
Project directory 'C:xxx\version' is not part of the build defined by settings file 'C:xxx\project\settings.gradle' . If this is an unrelated build, it must have its own settings file .
setting.gradle配置如下:
include ':module_home' include ':module_login' include ':base' include ':app' includeBuild "../version" rootProject.name = "mvvm_develop"
具体参考Demo
我这边升级了2020.03版本AndroidStudio,用的kotlin1.5.20, Gradle Version 7.0.2,上述有些使用jcenter的依赖需要注意下,新版AS已近完全抛弃了Jcenter
以上就完成了组件化的配置
参考资料 :
https://blog.csdn.net/yubo_725/article/details/118895551
抽离公用模块
有些功能是所有模块能公用的,比如:
这些模块一般不会去改动,和业务没有关联,但是所有模块都能公用,我们可以将其封装在base基础module中。
如果想将这个模块应用到多个项目上,可以单独发在公司的maven仓库中,也可以使用jitpack
上传到maven可以参考:https://blog.csdn.net/wang295689649/article/details/105219490
如何将单独的module发布到jitpack,参考:https://www.jianshu.com/p/b7552cf8983b,官方文档:https://jitpack.io/docs/ANDROID/
模块间通信
我们这里使用ARouter进行模块间的通信,对ARouter熟悉的同学就可以直接跳过了
基础接入:
在同一依赖管理中配置好:
const val ARoute = "1.5.2" const val ARouteCompiler = "1.5.2" const val ARoute = "com.alibaba:arouter-api:${Version.ARoute} " const val ARouteCompiler = "com.alibaba:arouter-compiler:${Version.ARouteCompiler} "
然后需要在所有使用了ARouter的模块中进行配置,注意java版本和kotlin有些不一样:
Java:
defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME : project.getName ()] } } } dependencies { ... implementation Dependency.DependencyImp .ARoute annotationProcessor Dependency.DependencyImp .ARouteCompiler }
Kotlin:
defaultConfig { ... kapt { arguments { arg("AROUTER_MODULE_NAME" , project.getName ()) } } } dependencies { ... implementation Dependency.DependencyImp .ARoute kapt Dependency.DependencyImp .ARouteCompiler }
在公共模块中统一管理路径,我这里创建了一个Constants类去同一管理路径:
object ARouterConstant { const val MainActivity = "/app/MainActivity" const val ARouterFragment = "/app/ARouterFragment" const val LoginActivity = "/login/LoginActivity" }
注意路径至少需要两级,建议使用/module name/class name
这样子配置
页面跳转
在activity上或者fragment上配置:
@Route (path = ARouterConstant.LoginActivity)class LoginActivity : BaseActivity(R.layout.activity_login) { } @Route (path = ARouterConstant.ARouterFragment)class ARouterFragment : BaseFragment(R.layout.fragment_arouter) { }
基础跳转:
ARouter .getInstance ().build (ARouterConstant.LoginActivity).navigation ()
携带参数跳转:
ARouter . getInstance() .build(ConstantARouter.LoginActivity) .with Long(ConstantParams.key1 , 666L) .with String(ConstantParams.key2 , "888" ) .navigation()
在目的页面获取参数:
private fun initParams () { val key1 = intent?.getLongExtra(ConstantParams.key1,0L ) val key2 = intent?.getStringExtra(ConstantParams.key2) }
可以使用@Autowired
注解自动注入,如下:
@Autowired @JvmField var key1 : Long = 0 L@Autowired (name = ConstantParams.key2)@JvmField var test : String = "" override fun initData(savedInstanceState : Bundle?) { ARouter .getInstance ().inject (this) }
使用@Autowired
注解的时候需要配合@JVMField
使用,在初始化的时候调用ARouter.getInstance().inject(this)
如果需要传递自定义对象,可以实现Serizable接口:
我们自定义一个数据bean: data class RouterBeanSerializable(val id :Int, val name :String) :Serializable 传递: ARouter . getInstance() .build(ConstantARouter.LoginActivity) .with Serializable(ConstantParams.key3 ,RouterBeanSerializable(id = 123,name = "哈哈哈哈" ) ) .navigation() 获取: val key3 = intent?.getSerializableExtra(ConstantParams.key3 ) 输出结果: key3:RouterBeanSerializable(id =123, name =哈哈哈哈)
对于没有实现序列化接口的对象,官方提供了withObject接口,我感觉没必要,有兴趣的可以参考这篇文章
ARouter获取Fragment实例:
ARouter是不支持fragment跳转的,只能获取fragment实例:
val fragment: Fragment = ARouter . getInstance() .build(ConstantARouter.ARouterFragment) .with Long("id" , key1 ) .with String("name" , test ) .navigation() as Fragment
更多关于更多ARouter的用法便不再阐述,请查看官方文档 ,我在Demo中也有进行演示
数据通信
上面介绍了使用ARouter进行页面跳转,和简单的页面间数据传递。如果我们不进行页面跳转,多module间有哪些数据通信的方式?
进行数据通信的方式有EventBus,数据持久化,进程间通信。大多不适用于我们的场景,我这里采用的是大佬推荐的向外暴露接口 的方式。
我们新建common module
,这个module主要是提供业务相关的公共依赖,我们在这里实现数据接口调用。
我们让common module使用api project(path: ':base' ) 这样其他模块只需要依赖一个common 就行了
这样做的好处是隔离每个module,不用使各个module之间相互依赖才能调用对方的服务
这里以login module
为例,提供获取用户信息的接口,先创建LoginInterface
接口:
interface LoginInterface { fun getToken () :String fun getUserName () :String fun getUserID () :Int }
ARouter
中提供了IProvider
接口服务,我们需要让自定义接口继承IProvider
接口。
interface LoginInterface : IProvider
接下来只需要在login module
中实现此接口就行了:
这里只是一些模拟数据 class LoginImpl : LoginInterface { override fun init (context: Context ?) { } override fun getToken () : String { return "this is login token" } override fun getUserName () : String { return "name" } override fun getUserID () : Int { return 123 } }
我们可以创建一个工具类去统一管理:
object LoginUtil { fun getLoginServer () :LoginInterface{ return ARouter.getInstance().navigation(LoginInterface::class .java) } }
在其他module中调用:
val token:String = getLoginServer() .getToken()
到此我们初步完成了组件化的搭建,更多使用的坑大家自行探索吧哈哈哈哈
参考:
https://juejin.cn/post/6881116198889586701
https://juejin.cn/post/6844903649102004231
https://juejin.cn/post/6844903687488274445、
上面三篇文章都挺值得大家参考的,感谢大佬们的付出
关于一些奇怪的问题
Moduel独立调试
moduel独立调试的时候需要设置不同的mainfest.xml文件,还要配置applicationId。
步骤如下:
1. 在Version模块中配置参数
object Dependency { object RunAlone{ const val base = false const val home = true const val login = false const val jitpack = false } .... }
2.在home moduel中新建Mainfest.xml文件
作为独立app的Mainfest文件个作为module的Mainfest文件是不一样的,我们需要创建两个
3.配置mdule_home的build.gradle文件
import com.xlu .version .Dependency plugins { id 'com.xlu.version' } if (Dependency.RunAlone .home .toBoolean ()) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' } apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' android { compileSdkVersion Dependency.ProjectConfig .compileSdkVersion buildToolsVersion Dependency.ProjectConfig .buildToolsVersion defaultConfig { if (Dependency.RunAlone .home .toBoolean ()) { applicationId Dependency.ProjectConfig .applicationId_home } ...... } ...... sourceSets { main { if (Dependency.RunAlone .home .toBoolean ()) { manifest.srcFile 'src/main/manifest/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' } } } } dependencies { implementation project(path: ':base' ) }
上述3步已经标明了该如何配置。
Application生命周期
我们通常在Application中执行一些初始化操作,比如sdk初始化。但是只有能独立运行的module才能拥有Application
如果我们将所有的初始化操作都方法在app application中,就会造成每次子module引入新的库都需要修改app module,有没有办法让子module自己执行初始化。
方式一:
反射实现,参考:https://juejin.cn/post/6844903649102004231#heading-12
方式二
生命周期分发,参考:
https://www.jianshu.com/p/59368ce8b670
https://juejin.cn/post/6881116198889586701#heading-26
方式三:
我们上面提到了其他module想外暴露服务,采用面向接口编程的方式,其他模块不能设计具体的实现类,我们在这里同样可以借鉴,Google提供了AutoSerive 服务,一个简单的例子:
新建一个接口:
interface Display { fun show () }
有很多的类实现了Display接口:
class phone :Diaplay{ override fun show(){ //show phone } } class monitor :Diaplay{ override fun show(){ //show monitor } }
我们需要在所有的实现类上加上注解:@AutoService(Display::class)
我们可以用过ServiceLoader加载出此接口所有的实现类
val mLoader: ServiceLoader<Display> = ServiceLoader.load (Display::class .java) mLoader.forEach { it.show () }
就是这么的简单,我们可以通过@AutoService来实现,这个我们的application初始化有什么关系呢?
创建相关接口:
interface ApplicationLifecycle { fun onAttachBaseContext (context: Context ) fun onCreate (application: Application ) fun onTerminate (application: Application ) fun init () }
创建一个代理类来实现此接口:
class LoadModuleProxy : ApplicationLifecycle { private var mLoader: ServiceLoader<ApplicationLifecycle> = ServiceLoader.load(ApplicationLifecycle::class .java) override fun onAttachBaseContext (context: Context ) { mLoader.forEach { Log.d("ApplicationInit" , it.toString()) it.onAttachBaseContext(context) } } override fun onCreate (application: Application ) { mLoader.forEach { it.onCreate(application) } } override fun onTerminate (application: Application ) { mLoader.forEach { it.onTerminate(application) } } override fun init () { mLoader.forEach { it.init () } } }
在BaseApplication中注册:
open class BaseApp :Application () { private val mLoadModuleProxy by lazy(mode = LazyThreadSafetyMode.NONE) { LoadModuleProxy() } companion object { private lateinit var baseApplication: BaseApp fun getContext () : Context { return baseApplication } } override fun attachBaseContext (base: Context ) { super .attachBaseContext(base) mLoadModuleProxy.onAttachBaseContext(base) } override fun onCreate () { super .onCreate() baseApplication = this mLoadModuleProxy.onCreate(this ) } override fun onTerminate () { super .onTerminate() mLoadModuleProxy.onTerminate(this ) } }
在子moduel中Application中实现ApplicationLifecycle接口
@AutoService(ApplicationLifecycle::class) class AppHome : ApplicationLifecycle { private val TAG = "AppHome" override fun onAttachBaseContext (context: Context ) { } override fun onCreate (application: Application ) { } override fun onTerminate (application: Application ) { } override fun init () { initSdkWorker() initSdkMain() } private fun initSdkWorker () { xLog.d(TAG, "initSdkWorker: " ) } private fun initSdkMain () { xLog.d(TAG, "initSdkMain: " ) } }
以上就可以实现子module自己愉快的玩耍了,AutoService使用参考:https://www.jianshu.com/p/086fe09188ea