
Android组件化开发:手把手教你搭建组件化项目
Android组件化项目搭建流程,以及build.gradle配置抽离,实现统一配置管理。
前言
看之前可以先看第五点思路总结,然后再回来看具体步骤。如果看起来觉得有点绕的话,我建议先去看看下面的视频,跟着视频搭项目,或者再回来看博客都可以。本博客中包含了build.gradle配置的抽离,统一管理,实际开发中用得到,感谢大家翻阅,欢迎评论区指正 ^_^
一、什么是组件化?
什么是组件化?这个不好说。我建议直接看视频。
Android组件化开发必须会,你掌握了吗?2021玩转安卓组件化开发,跟着我从架构设计到ARouter详解!_哔哩哔哩_bilibili
视频里将组件化分为四层:
在Android中,组件化开发是一种将整个应用拆分为多个相互独立、可插拔的组件的软件架构。这种架构的主要目标是提高代码的可维护性、可扩展性,以及多人协作的效率。在组件化开发中,通常有主工程组件、业务层组件、功能层组件和基础层组件。
-
主工程组件:
- 作用: 主工程组件是整个应用的入口和框架,负责组织和协调各个组件的运行。它通常包含应用的配置、初始化代码、全局路由、全局事件分发等。
- 分工: 主要负责应用的整体框架和控制,不涉及具体的业务逻辑。
- 示例: 在一个电商应用中,主工程组件可能包含全局用户登录状态管理、网络请求配置等。
-
业务层组件:
- 作用: 业务层组件是整个应用的核心,负责实现具体的业务功能。每个业务层组件可以看作是一个独立的业务模块,包含该业务领域的所有代码和资源。
- 分工: 负责实现具体的业务逻辑,与其他业务层组件相互独立。
- 示例: 在电商应用中,订单模块、商品模块、支付模块等可以作为独立的业务层组件。
-
功能层组件:
- 作用: 功能层组件提供通用的功能或服务,可以被多个业务层组件共享。这包括一些通用的 UI 组件、工具类、第三方库的封装等。
- 分工: 负责提供通用的功能和服务,减少重复代码,提高代码的复用性。
- 示例: 可以有一个功能层组件专门负责图片加载、缓存和显示,被多个业务层组件共享。
-
基础层组件:
- 作用: 基础层组件提供底层的基础设施支持,包括网络请求、数据库操作、权限管理等。这些功能对整个应用是必须的,但又不涉及具体的业务逻辑。
- 分工: 提供底层的基础设施,为业务层组件和功能层组件提供支持。
- 示例: 可以有一个基础层组件专门负责处理网络请求,被业务层组件和功能层组件共享。
举例说明:
考虑一个社交应用,其中包含消息模块、好友模块、个人资料模块等。在这个应用中,可以将每个模块作为一个业务层组件,共享一些通用的 UI 组件和网络请求工具,这些通用的功能可以放在功能层组件中。同时,可以有一个基础层组件处理底层的网络请求、数据库操作等。
主工程组件负责整个应用的初始化、全局路由等工作,确保各个组件能够协同工作。这样,每个模块可以独立开发、测试和维护,提高了代码的可维护性和可扩展性。
二、组件化项目搭建
1、项目结构总览
说明:
在这个项目中,基础层我定义了1个module,业务层定义了4个module。
我并没有创建功能层,功能层其实和基础层有点像,构建的过程是一样的,只是负责的职责不一样。
2、主工程创建
最开始新建一个Android项目后的结构就是主工程,上图去掉基础层和业务层就是开始新建项目后的样子。
3、业务层组件创建
1.业务层考虑到业务组件单独运行,所以我们选择Phone&Tablet
2.组件名可以按照自己的业务功能来自行定义
3.Module name的话建议将业务module都放在一个目录(modulesCore,可自定义)下,这样会使的项目结构更清晰。当然也可以不改直接建,但是当你过了一段时间回来再看项目的时候,左边一堆的module罗列在一起,你猜猜我是哪一层的module,你猜猜我是干嘛的。自行体会。
4.Package name的话,建议加一级,这样可以避免后续开发过程在的包名冲突,本来没有module的,com.hnucm.main。
成功后是这样的:(忽略其他三个module)
4、基础层组件搭建
1.基础层和功能层不会单独运行,跑组件,所以选择Android Library。
2.理由同业务组件第3点,lib_base名字可以自定。
3.理由同业务组件第4点。
成功构建后项目结构:
到这里组件化项目以及搭建好了,功能层和基础层的构建是一样的一毛一样,但需要注意的是Module name和Package name的项目文件夹名字别一样了。
这个名字,不在同一层,别建到同一个文件夹下面去了。
三、build.gradle抽离及统一管理
这一点可以不看,不影响项目搭建,但是我建议学习一下。
首先,我们看到各个module的build.gradle里的配置有很多是一样的,如果我们每新建一个module都要去配置build.gradle的话很烦琐,没必要。
1、抽离通用build.gradle
我们可以将相同的配置抽离出来,创建一个单独的.gradle文件,在新建的module中只要引入这个文件就行,其他的特有的再自行配置就行。
在项目根部新建一个config_build.gradle文件,文件名随意。
config_build.gradle代码:
/**
* 整个项目(app/module)的通用gradle配置
* 需要自行确定namespace。需要自行动态判定module是属于application(并配置applicationId),还是library。
* 默认依赖基础组件module,(:modulesBase:lib_base)
*/
apply plugin: 'org.jetbrains.kotlin.android'
android {
//rootProject 是指项目的根项目
compileSdk rootProject.ext.android.compileSdk
defaultConfig {
minSdk rootProject.ext.android.minSdk
targetSdk rootProject.ext.android.targetSdk
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
//使用implementation不传递依赖,可以防止重复依赖或依赖冲突
//公共依赖包
implementation project(':modulesBase:lib_base')
//ARouter 注解处理器 APT需要在每个使用的组件的build.gradle去添加,并在defaultConfig里添加project.getName。这里属于通用配置,一次配置完。
annotationProcessor rootProject.ext.arouter_compiler
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
别着急,马上给你解释。我们一步一步来。
(1)原始新建的时候最开始是这样的:(这是另一个项目的图,只是包名不一样,凑合看看吧)
最上面:
这种静态引入,我们改为这种动态的引入:
apply plugin: 'org.jetbrains.kotlin.android'
最初有两个,一个application,一个kotlin;我们只抽离出一个kotlin,原因是,我们不知道一个module他是要充当一个什么角色,他是一个单纯的library(基础层、功能层组件module),还是一个动态决定角色的组件(业务层module,调试状态可以单独运行单个组件时就是application,正式发布时就是library)。所以我们将决定他属于什么角色的决定权传递到具体的module,让他自己去决定,他是谁。
applicationId是只有application角色时才有的,所以也传递到具体的module。
还有这个也是一样,namespace rootProject.ext.applicationId.main。
核心思想:反正大家都有的就抽离出来,不一样的就到时候再写到具体的里面去。
(2)你应该发现这里不一样吧
最初的是一些数字,像这种:
你可以先不管这些什么rootProject.ext.android.minSdk,解释一下,在 Gradle 中,rootProject
是指项目的根项目,即顶层项目。 ext
是 ExtraPropertiesExtension
的缩写,是一个扩展属性的机制。通过将属性添加到 rootProject.ext
,你可以在整个 Gradle 构建中共享这些属性。相当于一个全局变量。
你可以按照抽离的核心思想先抽离出数字的通用.gradle文件,一样的,没什么影响。原因的话,下面再说。
(3)这个不用管,这是第三方框架ARouter的配置,原始没有。
2、引入到各module的build.gradle
我拿module的build.gradle代码来举例。
引入到业务层
业务层组件中的main的build.gradle,上面就建了这一个。
main组件的build.gradle:
//动态切换集成\组件模式
if (rootProject.ext.isDebug) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply from: "${rootProject.rootDir}/config_build.gradle"
android {
// 业务组件特定的配置
namespace rootProject.ext.applicationId.main
defaultConfig {
//组件模式需要applicationId
if (rootProject.ext.isDebug) {
applicationId rootProject.ext.applicationId.main
}
}
//动态切换集成/组件模式的AndroidManifest
sourceSets {
main {
if (rootProject.ext.isDebug) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
dependencies {
// 业务组件特定的依赖配置
}
解释一下代码:
(1)最上面,注释的很清楚了。
就是根据这个全局变量里的isDebug来判断属于那种情况。
如果是需要,注意是需要,我个人需要,我想要,不是非要这么写。比如我在主页写了一个页面,我想看下这个页面UI是什么效果的,如果项目体积很大的话,跑起来太慢了,我们想只跑这个页面所在的组件模块,不跑别的组件,跑一部分,这样就快了。
- 正式发布集成到App里这样配置:只要上面这一点就够了,特有的配置就自己加,自己加android{ // 业务组件特定的依赖配置},dependencies { // 业务组件特定的依赖配置 }等。
- application组件模式(组件可单独运行)就需要配置:
1.引入apply plugin: 'com.android.application'
在 Gradle 构建中,rootProject.rootDir
是指向项目根目录的引用。在 Gradle 构建脚本中,rootProject
是一个指向根项目的引用,而 rootDir
是 Project
对象的属性,表示项目的根目录。
注意:引入通用gradle文件的代码必须要在下面,如上图,原因是,在通用gradle文件中,我们没有确定他是什么角色,gradle文件是不完整的,需要先确定角色,再做配置,否则会报错。
Caused by:
org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException:
Could not find method android() for arguments [config_build_7j66y0z6502l5wetbnhl851jt$_run_closure1@64416c1]
on project ':modulesCore:main' of type org.gradle.api.Project.
意思是Gradle 在你的 config_build.gradle
中找不到 android {}
块。该文件仅包含部分配置,而不是完整的 Android 插件配置文件。
2、你要充当一个application那就需要给他一个id
3、 充当一个application,那他的AndroidManifest也需要配置
根据不同的角色选择不同的 AndroidManifest文件。
注意层级关系。
debug模式(单组件运行)下的AndroidManifest是这样的,就是本来的样子。复制一份到debug文件下。
debug文件里的AndroidManifest:
外层的AndroidManifest:
引入到主工程、功能层、基础层module
主工程:build.gradle中只需要引入两行代码:
apply plugin: 'com.android.application'
apply from: "${rootProject.rootDir}/config_build.gradle"
功能层、 基础层:build.gradle中也只需要引入两行代码:
apply plugin: 'com.android.library'
apply from: "${rootProject.rootDir}/config_build.gradle"
注意:注意对比原始配置和抽离出来的通用gradle文件的区别,将特有的自行添加的特定的module。
3、抽离出全局统一配置文件
说明一下,其实到上面那一步就可以了,将各个module中相同的配置抽离成一个通用gradle文件,然后在新建的module中引入,新建的module自己需要的,不同于通用gradle配置的就自己在自己的build.gradle中去配置。
但是我们可以更进一步将通用gradle中的各种配置分离一下,像sdk版本、第三方库等等。
在项目根部新建一个config.gradle文件
config.gradle代码:
/**
* 全局统一配置文件
* ext 是 ExtraPropertiesExtension 的缩写,是一个扩展属性的机制。通过将属性添加到 rootProject.ext,你可以在整个 Gradle 构建中共享这些属性。
*/
ext {
//当它为true时,调试模式,组件可以单独运行。如果是false,正式编译打包的模式。
isDebug = true
android = [
compileSdk : 33,
applicationId: "com.hnucm.gdesign_android",
minSdk : 24,
targetSdk : 33,
versionCode : 1,
versionName : "1.0"
]
applicationId = [
"app" : "com.hnucm.gdesign_android",
"main" : "com.hnucm.module.main",
"login" : "com.hnucm.module.login",
"message": "com.hnucm.module.message",
"mine" : "com.hnucm.module.mine"
]
//SDk中核心的库
library = [
corektx : "androidx.core:core-ktx:1.8.0",
appcompat : "androidx.appcompat:appcompat:1.4.1",
material : "com.google.android.material:material:1.5.0",
constraintlayout: "androidx.constraintlayout:constraintlayout:2.1.3"
]
//第三方的库
arouter_api = "com.alibaba:arouter-api:1.5.2"
//ARouter 的注解处理器
arouter_compiler = "com.alibaba:arouter-compiler:1.5.2"
gson = "com.google.code.gson:gson:2.8.6"
}
前面说到的rootProject.ext就是这个文件里的,将这个文件引入到项目的build.gradle中
这样做就可以在任何一个module中的build.gradle使用rootProject.ext.XXX来拿到config.gradle中的属性值,就像一个静态变量一样。就像通用config_build.gradle中的:
这么做有什么好处呢?
如果我们后续需要更新一个sdk版本,或者更新第三方库的版本,我们只需要去修改这个全局的配置文件就行,当然前提是其他的module的build.gradle都是像这样调用这个全局来配置的,如上图。
还有一点就是,如果我新开了一个项目,我需要去用到一些第三方的库,像什么gson、glide等等,我还要一个一个去找这些第三方的库。很麻烦,现在我把这些配置都放在一个全局的配置文件里,我新开一个项目,我直接把这个配置文件复制到新项目里,引入到项目的build.gradle里,我就可以在任何module里通过rootProject.ext.XXX来拿到我想要的各种东西,包括但不限于第三方库,而且这些属性于某个项目的关联性很低,可重用性很高,此乃开发偷懒利器,嘿嘿嘿。
4、使用全局统一配置文件
前提:需要在项目的build.gradle中引入全局统一配置文件。
全局统一配置文件的使用很简单,通过rootProject.ext.XXX来使用就行。
当然也可以定义一个变量来代替他,简化代码。
这里讲一下api和implementation的区别,两者都是引入库的操作,区别在与api会传递依赖,implementation不会。什么意思呢?
(下面说的都是module)业务层main依赖了基础层的lib_base,如果lib_base使用了api来添加库,比如上图gson,main依赖了lib_base,lib_base使用api引入gson,那么lib_base会将gson也传递给main,相当于main也引入了gson。而使用implementation就不会传递,相当于java的private,只有自己有。
这里我们需要注意的就是,要避免重复依赖和依赖冲突。比如main依赖了lib_base,lib_base传递了gson给他,main又依赖另外一个module B,B也用api引入了gson,也传递给了main,这时候就出现了依赖冲突,如果两个版本一样,就是重复依赖。如果使用全统一配置,就会大幅减少出现依赖冲突(组件化开发是多人开发,有些人不一定遵循规范)。
建议:在全局的公共依赖包module里使用api,其他的module中使用implementation,这样能减少依赖传递造成的种种依赖和版本问题。
四、层级依赖添加
添加依赖包很简单,看图。
在这个项目中,我将lb_base作为了一个公共依赖包,大家都依赖他。实际开发中看具体需求来引入包。引号里是要依赖的包的位置,如果不知道怎么写呢,可以到settings.gradle文件中去查。
图上面的这个是国内镜像,有时候下包很慢,dddd。
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/public' }
五、总结
梳理一下整体思路:
- 首先,我们将各个层级module new出来。
- 其次,将各个module中的build.gradle文件中相同的配置抽离成一个通用config_build.gradle文件,再引入到各个module,各个module特有的配置再针对性配置。
- 然后,将通用config_build.gradle文件中的版本配置、SDK库、第三方库等值(存储的都是一些键值)抽离成一个全局统一配置文件config.gradle文件,引入到项目的build.gradle。所有的module、通用config_build.gradle文件都使用全局统一配置文件 config.gradle文件中的值来配置各个配置。这是一种使用关系。
- 最后,添加各层级module依赖,按照需求去依赖,注意区别api和implementation添加依赖,避免产生依赖问题。建议使用层级依赖使用implementation,只在公共依赖包中引入库时用api。(包依赖包用implementation,公共依赖包内添加第三方库等用api)
只做总结的第一和第四点就可以搭建出组件化的项目。组件间通信的话用第三方框架ARouter来实现。
六、附录
主工程module app的build.gradle 代码:
apply plugin: 'com.android.application'
apply from: "${rootProject.rootDir}/config_build.gradle"
android {
namespace rootProject.ext.applicationId.app
defaultConfig {
applicationId rootProject.ext.applicationId.app
}
}
dependencies {
}
业务层module main的build.grade代码:
//动态切换集成\组件模式
if (rootProject.ext.isDebug) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply from: "${rootProject.rootDir}/config_build.gradle"
android {
// 业务组件特定的配置
namespace rootProject.ext.applicationId.main
defaultConfig {
//组件模式需要applicationId
if (rootProject.ext.isDebug) {
applicationId rootProject.ext.applicationId.main
}
}
//动态切换集成/组件模式的AndroidManifest
sourceSets {
main {
if (rootProject.ext.isDebug) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
dependencies {
// 业务组件特定的依赖配置
}
基础层module lib_base的build.gradle代码:
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
def cfg = rootProject.ext
android {
namespace 'com.hnucm.library.lib_base'
compileSdk cfg.android.compileSdk
defaultConfig {
minSdk cfg.android.minSdk
targetSdk cfg.android.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
//只要依赖lib_base,它就会依赖这些SDK的库,向上传递。api 依赖传递 ,implementation 不会传递依赖
//SDK核心库
api cfg.library.corektx
api cfg.library.appcompat
api cfg.library.material
api cfg.library.constraintlayout
//第三方库
api cfg.arouter_api
//注解处理器,APT需要在每个使用的组件的build.gradle去添加,并在defaultConfig里添加project.getName
// annotationProcessor cfg.arouter_compiler
api cfg.gson
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
通用配置文件config_build.gradle代码:
/**
* 整个项目(app/module)的通用gradle配置
* 需要自行确定namespace。需要自行动态判定module是属于application(并配置applicationId),还是library。
* 默认依赖基础组件module,(:modulesBase:lib_base)
*/
apply plugin: 'org.jetbrains.kotlin.android'
android {
//rootProject 是指项目的根项目
compileSdk rootProject.ext.android.compileSdk
defaultConfig {
minSdk rootProject.ext.android.minSdk
targetSdk rootProject.ext.android.targetSdk
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
//使用implementation不传递依赖,可以防止重复依赖或依赖冲突
//公共依赖包
implementation project(':modulesBase:lib_base')
//ARouter 注解处理器 APT需要在每个使用的组件的build.gradle去添加,并在defaultConfig里添加project.getName。这里属于通用配置,一次配置完。
annotationProcessor rootProject.ext.arouter_compiler
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
全局统一配置文件config.gradle:
/**
* 全局统一配置文件
* ext 是 ExtraPropertiesExtension 的缩写,是一个扩展属性的机制。通过将属性添加到 rootProject.ext,你可以在整个 Gradle 构建中共享这些属性。
*/
ext {
//当它为true时,调试模式,组件可以单独运行。如果是false,正式编译打包的模式。
isDebug = true
android = [
compileSdk : 33,
applicationId: "com.hnucm.gdesign_android",
minSdk : 24,
targetSdk : 33,
versionCode : 1,
versionName : "1.0"
]
applicationId = [
"app" : "com.hnucm.gdesign_android",
"main" : "com.hnucm.module.main",
"login" : "com.hnucm.module.login",
"message": "com.hnucm.module.message",
"mine" : "com.hnucm.module.mine"
]
//SDk中核心的库
library = [
corektx : "androidx.core:core-ktx:1.8.0",
appcompat : "androidx.appcompat:appcompat:1.4.1",
material : "com.google.android.material:material:1.5.0",
constraintlayout: "androidx.constraintlayout:constraintlayout:2.1.3"
]
//第三方的库
arouter_api = "com.alibaba:arouter-api:1.5.2"
//ARouter 的注解处理器
arouter_compiler = "com.alibaba:arouter-compiler:1.5.2"
gson = "com.google.code.gson:gson:2.8.6"
}
项目build.gradle代码:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.0.0' apply false
id 'com.android.library' version '8.0.0' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
}
//引入全局配置文件
apply from: "config.gradle"
module main中 debug 中的AndroidManifest.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GDesign_android">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
module main中 最外层的AndroidManifest.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name=".MainActivity"/>
</application>
</manifest>
更多推荐
所有评论(0)