这里的组件化每个模块可以单独运行、打包、测试,可随意拆卸、随意组装,既不互相依赖又可以互相调用。是通过在一个Project下通过创建多个Module实现的。
假设三个模块:App(可以认为是首页或是空壳)、ShoppingCar、Order。
问题一:每个Module下都有build.gradle,怎么统一管理版本号和引入包?
- 在Project目录下创建
config.gradle
,并且在Project目录下的build.gradle
中引入:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle"
buildscript {
...
}
allprojects {
...
}
...
- 在
config.gradle
中统一管理:
// 添加多个自定义属性,可以通过ext代码块
ext {
androidId = [
compileSdkVersion: 28,
buildToolsVersion: "29.0.0",
minSdkVersion : 21,
targetSdkVersion : 28,
versionCode : 1,
versionName : "1.0"
]
appId = [
app : "com.yu.modular",
]
supportLibrary = "28.0.0"
dependencies = [
"appcompat" : "com.android.support:appcompat-v7:${supportLibrary}",
"recyclerview" : "com.android.support:recyclerview-v7:${supportLibrary}",
"constraint" : "com.android.support.constraint:constraint-layout:1.1.3"
]
}
- app的
build.gradle
中使用:
apply plugin: 'com.android.application'
// 赋值与引用
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies
android {
compileSdkVersion androidId.compileSdkVersion
buildToolsVersion androidId.buildToolsVersion
defaultConfig {
applicationId appId.applicationId
minSdkVersion androidId.minSdkVersion
targetSdkVersion androidId.targetSdkVersion
versionCode androidId.versionCode
versionName androidId.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 标准写法
// implementation group: 'com.android.support', name:'appcompat-v7', version:'28.0.0'
// 简写
// implementation 'com.android.support:appcompat-v7:28.0.0'
// 依赖第三方库最简洁的方式:
support.each { k, v -> implementation v } // 上面已经定义过support,是在Project的config.gradle中统一管理的
}
其他的Module的build.gradle
中也是一样用,只是如果是library的话不需要applicationId
问题二:怎么组合Module? 怎么单独运行Module?
如果创建Module的是Phone & Tablet Module, 那么这个Module是可以单独运行的,但是怎么集成进app?这样的Module直接implementation会报循环引用的错误。
如果创建Module的是Android Library,那这个Module是可以在app中引入的,但是这样的Module如何独立运行?
这里就需要动态去切换Module的类型。对比Phone Module和Android Library的build.gradle
可看出不同处只有两点:
- Phone Module中:
apply plugin: 'com.android.application'
,而Android Library中:apply plugin: 'com.android.library'
- Phone Module有applicationId,而Android Library没有。
要完成在Module的类型切换,只需要动态修改这两个地方。
在config.gradle
中定义一个标签用来标示组件化还是集成化,并且统一管理Module的applicationId:
ext {
// false: 组件化模式(子模块可以独立运行),true :集成化模式(打包整个项目apk,子模块不可独立运行)
isRelease = false
···
appId = [
app : "com.yu.modular",
order : "com.yu.order",
shopping : "com.yu.shopping"
]
...
}
app中不需要修改,无论什么环境下都是Phone Module,下面以order模块的build.gradle
为例,按上面两个不同点,这里需要修改一下两处:
if (isRelease) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
...
def appId = rootProject.ext.appId
android {
...
defaultConfig {
if (!isRelease) {
applicationId appId.order
}
...
}
...
}
...
ShoppingCar也需要做同样修改,然后在app的build.gradle
中加入:
apply plugin: 'com.android.application'
...
dependencies {
...
if (isRelease) {
implementation project(':order')
implementation project(':shoppingcar')
}
}
这里有两点需要注意:
- 创建Module的时候建议选择Phone & Tablet Module,如果选择的是Android Library,
AndroidManifest
文件和res
目录内容不全,需要手动修改。 - Module中java和res目录下的命名前面尽量加上Module名,类似:
Order_MainActivity.java
、order_activity_main.xml
这样,方便区分。
完成以上操作,只要修改config.gradle
中的isRelease
,就可以实现集成化和插件化的切换了。isRelease=true
时,打包出来是一个apk,除app外其他的module不可以单独运行;isRelease=false
时,打包出来有几个module做过上述配置就有几个apk,所有的module都可以单独运行。
问题三:Debug时候的一些类并不想在Release的时候打包进去,怎么去隔离这些文件?比如单独模块提供给测试的时候,需要有个Activity去模拟触发其他模块传递过来的操作,而这个Activity打包的时候并不需要。
这里需要为这些module在不同环境下创建不同的AndroidManifest文件,并且设置Release环境下的过滤目录,以Order模块为例,在Order的build.gradle
文件中添加:
...
android {
...
// 配置资源路径,方便测试环境,打包不集成到正式环境
sourceSets {
main {
if (!isRelease) {
// 如果是组件化模式,需要单独运行时
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
// 集成化模式,整个项目打包apk
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
// release 时 debug 目录下文件不需要合并到主工程
exclude '**/debug/**'
}
}
}
}
}
...
对应创建出文件和目录:
这样在debug包下的文件,就不会打包进Release时的apk中,如果是Activity,也只需要配置Debug环境下的
AndroidManifest
就可以。这里就不贴图了,可以在打包出来的apk中验证下(验证时关闭分包更好找一些)。
问题四:公共基础库怎么引用,比如图片下载,网络请求等?
这里没什么特殊处理,新创建一个Android Library,比如:common
,然后再需要用到的module的build.gradle
中跟引入其他三方库一样,添加:
..
dependencies {
...
implementation project(':common') // 公共基础库
}
问题五:集成化的Module之间怎么交互?
集成化环境下app模块可以调用其他的模块,因为isRelease==true
时会引入其他模块:
if (isRelease) {
implementation project(':order')
implementation project(':shoppingcar')
}
但是其他模块之间的互相调用呢?
EventBus?很杂,不方便管理。
反射?高版本api有可能被@hide。
隐示意图?广播? 都不够舒服,没人愿意这么写。
先看下面两种实现方式
- 用类加载
Class clazz = Class.forName("com.yu.modular.shoppingcar.ShoppdingCar_MainActivity");
Intent intent = new Intent(this, clazz);
startActivity(intent);
这样写很容易写错先不说,一旦一模块修改了包名或类名,其他模块都要改,很难维护。
- 在公共库
common
中对Activity进行统一管理,因为各模块都引入了公共库common
,所以都可以对common
进行操作。
公共库的PathManager:
public class PathManager {
/**
*
* @param groupName 组名,如:"order"
* @param pathName 路劲名,如:"Order_MainActivity"
* @param clazz 类对象,如:Order_MainActivity.class
*/
public static void set(String groupName, String pathName, Class<?> clazz) {
...
}
/**
*
* @param groupName 组名
* @param pathName 路径名
* @return 跳转目标的class类对象
*/
public static Class<?> get(String groupName, String pathName) {
...
}
...
}
以什么形式存?
这里把Activity路径和类封装成一个Bean去储存:
public class PathBean {
private String path;
private Class clazz;
public PathBean(String path, Class clazz) {
this.path = path;
this.clazz = clazz;
}
...
}
Map里的存储结构类似这样:
[
app : [app_PathBean1, app_PathBean2, app_PathBean3],
shoppingCar: [shoppingCar_PathBean1, shoppingCar_PathBean2, shoppingCar_PathBean3],
order: [order_PathBean1, order_PathBean2, order_PathBean3]
]
什么时候去存?如果在Activity中去存肯定是来不及的,所以app(主模块)的Application的onCreate方法中去存:
public class AppApplication extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
PathManager.set("app", "MainActivity", MainActivity.class);
PathManager.set("shoppingcar", "ShoppingCar_MainActivity", MainActivity.class);
PathManager.set("order", "Order_MainActivity", MainActivity.class);
}
}
跳转时这样调用:
Class clazz = PathManager.get("order", "Order_MainActivity");
Intent intent = new Intent(this, clazz);
startActivity(intent);
这样就完成集成化模块间通讯了。
项目地址
手动去管理这个存储的Map还是不太舒服,有没有什么办法可以自动生成?
有,使用APT+JavaPoet技术。Android组件化实现方案(二)