组件化思想
组件化对于各个领域都已经不是新鲜的词汇了,在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模块中的引用:
// build.gradle
def supports = rootProject.ext.dependencies
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation supports.appcompat
implementation supports.constraintlayout
// supports 依赖
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 {
// 在 app 模块需要通过 id 引用这个插件 ,修改为version模块包名
id = 'com.xlu.version'
// 实现这个插件的类的路径
implementationClass = 'com.xlu.version.VersionPlugin'
}
}
}
在此模块下创建VersionPlugin 类,如下:
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* TODO 这里什么都不用做,创建就行了
*/
class 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{
// Android---------------------------------------------------------------
const val Junit = "4.13"
const val Material = "1.2.0" // 材料设计UI套件
// AndroidX--------------------------------------------------------------
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"
// Kotlin----------------------------------------------------------------
const val Kotlin = "1.5.10"
const val Coroutines = "1.5.0" // 协程
// JetPack---------------------------------------------------------------
const val Lifecycle = "2.3.1" // Lifecycle相关(ViewModel & LiveData & Lifecycle)
const val Hilt = "2.35.1" // DI框架-Hilt
const val HiltAndroidx = "1.0.0"
}
object DependencyImp{
//Android
const val Junit = "junit:junit:${Version.Junit}"
const val Material = "com.google.android.material:material:${Version.Material}"
//AndroidX
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}"
//Kotlin
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}"
//Jetpack
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文件
//setup1
import com.example.plugin.Dependency
// setup2
plugins {
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'
//Android
api Dependency.DependencyImp.AppCompat
api Dependency.DependencyImp.Material
api Dependency.DependencyImp.AndroidXLeagcy
api Dependency.DependencyImp.ActivityKtx
api Dependency.DependencyImp.FragmentKtx
api Dependency.DependencyImp.MultiDex
//Kotlin
api Dependency.DependencyImp.Kotlin
api Dependency.DependencyImp.CoreKtx
//Kotlin协程核心库
api Dependency.DependencyImp.CoroutinesCore
api Dependency.DependencyImp.CoroutinesAndroid
//navigation
api Dependency.DependencyImp.NavigationFragment
api Dependency.DependencyImp.NavigationUI
//lifecycle
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" // 阿里路由 APT
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) {
}
基础跳转:
//跳转到Login模块 -> LoginActivity
ARouter.getInstance().build(ARouterConstant.LoginActivity).navigation()
携带参数跳转:
ARouter.getInstance().build(ConstantARouter.LoginActivity)
.withLong(ConstantParams.key1, 666L)
.withString(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 = 0L
@Autowired(name = ConstantParams.key2)
@JvmField var test : String = ""
override fun initData(savedInstanceState: Bundle?) {
// Start auto inject.
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)
.withSerializable(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)
.withLong("id", key1)
.withString("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?) {
//此方法为IProvider接口提供
}
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)//如果只有一个实现,这种方式也可以
//return ARouter.getInstance().build(ConstantARouter.LoginItfImpl).navigation() as LoginInterface
}
}
在其他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 {
//是否允许module独立允许
object RunAlone{
const val base = false
const val home = true //以home独立允许为例
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'
}
//setup1:
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 {
//setup2:作为独立app需要配置applicationId
if (Dependency.RunAlone.home.toBoolean()) {
applicationId Dependency.ProjectConfig.applicationId_home
}
......
}
......
//setup3:
sourceSets {
main {
// 独立调试与集成调试时使用不同的 AndroidManifest.xml 文件
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()
}
- 创建一个代理类来实现此接口:
//注意这里不需要注解@AutoService
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
以上代码都在mvvm_develop中可以找到,文章中为了简化省略了一些东西,Demo中有完整的代码。本来上周就应该搞完的,咕咕咕到了现在,项目整体还在完善中,下一步是引入JetPack组件和一些工具来帮助开发,欢迎大佬们指点。
|