本章内容主要介绍以下内容:
- 什么是组件化,为什么需要组件化。
- 集成环境及组件环境自动部署配置。
1. 早期的单一分层模式
所有的代码都写在app模块中不同的包里面,这就会导致很多问题(低内聚、高耦合、无重用、层次混乱):
- 无论分包怎么做,随着项目增大,项目失去层次感,后面接手的人扑街
- 包名约束太弱,稍有不注意,就会不同业务包直接互相调用,代码高耦合
- 多人开发在版本管理中,容易出现代码覆盖冲突等问题
2. 什么是组件化,为什么需要组件化
组件化的意义:不相互依赖,可以相互交互,任意组合,高度解耦,自由拆卸,自由组装,重复利用,分层独立化
组件化后,所有的 module 都平起平坐,有人说 app 的 module 在组件化中是个壳子,这也是为什么成为 app 壳工程的原因。
3. 集成环境及组件环境自动部署配置
在项目的开发过程中,一定要去优化我们的gradle文件,如:把公用的内容抽取,关于一切与“正式环境”,“测试环境” 相关的应该用Gradle进行配置
Phone Module 和 Android Library 区别、切换:
组件化开发规范: order 前缀(src类 和 res资源) personal 前缀( src类 和 res资源)app 可不改,让它默认。
组件化开发的临时代码,集成化打包时动态隔离代码:
// 配置资源路径,方便测试环境,打包不集成到正式环境
sourceSets {
main {
if (!isRelease) {
// 如果是组件化模式,需要单独运行时
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
// 集成化模式,整个项目打包apk
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
// release 时 debug 目录下文件不需要合并到主工程
exclude '**/debug/**'
}
}
}
}
注意事项: src/main/debug/AndroidManifest.xml
子模块包名/debug/测试代码.java
源码配置
config.gradle 公共一份 gradle:
// Groovy语言 面向对象 if for
// 把一些公用的,共用的,可扩展的,加入到这里面来
// 整个App项目的Gradle配置文件
// ext 自定义增加我们的内容
//扩展块
ext {
// 开发环境 / 生产环境(测试/正式)
isRelease = true
// 建立Map存储,对象名、key都可以自定义,groovy糖果语法,非常灵活
androidID = [compileSdkVersion: 30,
buildToolsVersion: "30.0.2",
applicationId : "com.wuc.moduleproject",
minSdkVersion : 21,
targetSdkVersion : 30,
versionCode : 1,
versionName : "1.0"]
appId = ["app" : "com.wuc.moduleproject",
"order" : "com.wuc.order",
"personal": "com.wuc.personal"]
// 测试环境,正式环境 URL
url = ["debug" : "https://11.22.33.44/debug",
"release": "https://11.22.33.44/release"]
// 依赖相关的
dependenciesID = ["appcompat" : "androidx.appcompat:appcompat:1.2.0",
"constraintlayout": "androidx.constraintlayout:constraintlayout:2.0.4",
"material" : "com.google.android.material:material:1.2.1",
"vectordrawable" : "androidx.vectordrawable:vectordrawable:1.1.0",
"fragment" : "androidx.navigation:navigation-fragment:2.2.2",
"ui" : "androidx.navigation:navigation-ui:2.2.2",
"extensions" : "androidx.lifecycle:lifecycle-extensions:2.2.0",
/* "junit" : "junit:junit:junit:junit:4.13.1",
"runner" : "androidx.test:runner:1.2.0",
"testjunit" : "androidx.test.ext:junit:1.1.2",
"espresso" : "androidx.test.espresso:espresso-core:3.3.0"*/]
}
project build.gradle:
// 根目录下的build.gradle
引入 apply from : 'config.gradle'
app build.gradle:
plugins {
id 'com.android.application'
}
// 完整的方式 性能
def app_android = rootProject.ext.androidID
android {
compileSdkVersion app_android.compileSdkVersion
buildToolsVersion app_android.buildToolsVersion
defaultConfig {
applicationId appId.app
minSdkVersion app_android.minSdkVersion
targetSdkVersion app_android.targetSdkVersion
versionCode app_android.versionCode
versionName app_android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// 让我的Java代码也可以用
// 给Java代码暴漏,标记,正式环境 和 测试环境 的标记
// 组件化 和 集成化 的时候需要
buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
}
buildTypes {
debug {
buildConfigField("String", "debug", "\"${url.debug}\"")
}
release {
buildConfigField("String", "release", "\"${url.release}\"")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
/* implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'*/
dependenciesID.each { k, v -> implementation v }
// 公共基础库
implementation project(":common")
// 如果是集成化模式,做发布版本时。各个模块都不能独立运行了
if (isRelease) {
implementation project(':order') // 这样依赖时,必须是集成化,有柱状图, 否则会循环依赖问题
implementation project(':personal') // 这样依赖时,必须是集成化,有柱状图, 否则会循环依赖问题
}else {
// 不需要做事情,组件环境,不需要依附App壳
}
}
组件模块 order build.gradle:
// 如果是发布版本时,各个模块都不能独立运行
if (isRelease) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
// 定义变量
def app_android = this.rootProject.ext.androidID
android {
compileSdkVersion app_android.compileSdkVersion
buildToolsVersion app_android.buildToolsVersion
defaultConfig {
// 如果是集成化模式,不能有applicationId
if (!isRelease) {
// 组件化模式能独立运行才能有applicationId
// applicationId "com.wuc.order"
applicationId appId.order
}
minSdkVersion app_android.minSdkVersion
targetSdkVersion app_android.targetSdkVersion
versionCode app_android.versionCode
versionName app_android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// 这个方法接收三个非空的参数,第一个:确定值的类型,第二个:指定key的名字,第三个:传值(必须是String)
// 为什么需要定义这个?因为src代码中有可能需要用到跨模块交互,如果是组件化模块显然不行
// 切记:不能在android根节点,只能在defaultConfig或buildTypes节点下
buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// 配置资源路径,方便测试环境,打包不集成到正式环境
sourceSets {
main {
if (!isRelease) {
// 如果是组件化模式,需要单独运行时
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
// 集成化模式,整个项目打包apk
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
// release 时 debug 目录下文件不需要合并到主工程
exclude '**/debug/**'
}
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
/* implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'*/
dependenciesID.each { k, v -> implementation v }
// 公共基础库
implementation project(":common")
}
组件模块 personal build.gradle:
// 如果是发布版本时,各个模块都不能独立运行
if (isRelease) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
// 定义变量
def app_android = this.rootProject.ext.androidID
android {
compileSdkVersion app_android.compileSdkVersion
buildToolsVersion app_android.buildToolsVersion
defaultConfig {
// 如果是集成化模式,不能有applicationId
if (!isRelease) {
// 组件化模式能独立运行才能有applicationId
// applicationId "com.wuc.order"
applicationId appId.personal
}
minSdkVersion app_android.minSdkVersion
targetSdkVersion app_android.targetSdkVersion
versionCode app_android.versionCode
versionName app_android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// 这个方法接收三个非空的参数,第一个:确定值的类型,第二个:指定key的名字,第三个:传值(必须是String)
// 为什么需要定义这个?因为src代码中有可能需要用到跨模块交互,如果是组件化模块显然不行
// 切记:不能在android根节点,只能在defaultConfig或buildTypes节点下
buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// 配置资源路径,方便测试环境,打包不集成到正式环境
sourceSets {
main {
if (!isRelease) {
// 如果是组件化模式,需要单独运行时
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
// 集成化模式,整个项目打包apk
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
// release 时 debug 目录下文件不需要合并到主工程
exclude '**/debug/**'
}
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
dependenciesID.each { k, v -> implementation v }
// 公共基础库
implementation project(":common")
/* implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'*/
}
配置好后,测试模式下,各个模块可以独立运行。正式环境下,各个模块都不能独立运行,需依附 app 壳,并屏蔽 debug 的测试代码,减少 APK 体积。
4. 组件化中 子模块交互方式
- 使用 EventBus的方式,缺点是:EventBean维护成本太高,不好去管理
- 使用广播的方式,缺点是:不好管理,都统一发出去了
- 使用隐士意图方式,缺点是:在 AndroidManifest.xml 里面配置xml写的太多了 -- 使用类加载方式,缺点就是,容易写错包名类名,缺点较少
- 使用全局 Map 的方式,缺点是,要注册很多的对象
类加载技术交互
// 类加载跳转,可以成功。维护成本较高且容易出现人为失误
try {
Class targetClass = Class.forName("com.wuc.personal.PersonalMainActivity");
Intent intent = new Intent(this, targetClass);
intent.putExtra("name", "Wuc");
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
全局Map记录信息的方式交互
Class<?> targetActivity =
RecordPathManager.startTargetActivity("personal", "Personal_MainActivity");
startActivity(new Intent(this, targetActivity));
/**
* 路径对象(公共基础库中,所有子模块都可以调用)
* 如
* path :"personal/PersonalMainActivity"
* clazz:"PersonalMainActivity.class"
*/
public class PathBean {
private String path; // personal/PersonalMainActivity
private Class clazz; // PersonalMainActivity.class
public PathBean() {
}
public PathBean(String path, Class clazz) {
this.path = path;
this.clazz = clazz;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
}
/**
* 全局路径记录器(根据子模块进行分组)
*
* 组名:app,order,personal
* 详情order=[OrderMainActivity,OrderMainActivity2,OrderMainActivity3]
*/
public class RecordPathManager {
/**
* 先理解成 仓库
* group: app,order,personal
*
* order:
* OrderMainActivity1
* OrderMainActivity2
* OrderMainActivity3
*/
private static Map<String, List<PathBean>> maps = new HashMap<>();
/**
* 将路径信息加入全局Map
*
* @param groupName 组名,如:"personal"
* @param pathName 路劲名,如:"PersonalMainActivity"
* @param clazz 类对象,如:PersonalMainActivity.class
*/
public static void addGroupInfo(String groupName, String pathName, Class<?> clazz) {
List<PathBean> list = maps.get(groupName);
if (null == list) {
list = new ArrayList<>();
list.add(new PathBean(pathName, clazz));
// 存入仓库
maps.put(groupName, list);
} else {
// 存入仓库
maps.put(groupName, list);
}
}
/**
* 只需要告诉我,组名 ,路径名, 就能返回 "要跳转的Class"
*
* @param groupName 组名 oder
* @param pathName 路径名 OrderMainActivity1
* @return 跳转目标的class类对象
*/
public static Class<?> startTargetActivity(String groupName, String pathName) {
List<PathBean> list = maps.get(groupName);
if (list == null) {
Log.d("RecordPathManager", "startTargetActivity 此组名得到的信息,并没有注册进来哦...");
return null;
}
// 遍历 寻找 去匹配 “PathBean”对象
for (PathBean pathBean : list) {
if (pathName.equalsIgnoreCase(pathBean.getPath())) {
return pathBean.getClazz();
}
}
return null;
}
/**
* 清理、回收
*/
public static void recycleGroup() {
maps.clear();
maps = null;
System.gc();
}
}