随着很多公司的业务越来越多样化和复杂化,组件化开发也越来越流行,为我们调试代码和多人协助开发带来了巨大的好处,我们肯定会遇到下面几个痛点:
- 组件是否单独运行
if (isDebug) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
通过gradle.properties配置isDebug变量来处理组件作为app还是lib的存在。
- 组件与组件之间的Manifest合并问题
sourceSets {
main {
if (rootProject.ext.isBuildApp) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
//移除debug资源
manifest.srcFile 'src/main/release/AndroidManifest.xml'
java {
exclude 'debug/**'
}
}
}
}
通过config.gradle中的变量判断该moudle到底用哪个AndroidManifest.xml文件,这两个的区别在于进入的时候是否需要配置activity启动intent-filter。
其实上面的这些操作我们都可以通过Gradle插件来帮我们管理组件,已经有人帮我们实现了这个想法Gradle自动实现Android组件化模块构建,为了深入理解构建方式本文分析下该开源项目calces-gradle-plugin,阅读本文前可以先阅读关于Groovy的语法的文章Gradle自定义Plugin(上)。
我们先看下项目结构目录:
1处是定义的配置的moudle信息模型层,2是处理组件与组件之间的Manifest合并问题的,3是针对app壳工程编写的Plugin插件 ,4是针对非壳工程moudle编写的Plugin插件 AppConfigPlugin和ModulesConfigPlugin编写完后需要在下面的目录对应配置下。我们先看AppConfigExt类,这个一个gradle配置信息入口的类
class AppConfigExt {
boolean isDebug = false //组件单独开启开关
//容纳object的容器,它的特点是它的内部使用SortedSet实现的必须有一个public的构造函数,
// 接受string作为一个参数,必须有一个叫做name 的property
NamedDomainObjectContainer<AppExt> apps //宿主载体
NamedDomainObjectContainer<LibraryExt> modules //组件
AppConfigExt(Project project){
apps = project.container(AppExt) //创建object的容器对象
modules = project.container(LibraryExt)
}
def isDebug(boolean isDebug){
this.isDebug = isDebug
}
def apps(Closure closure){
apps.configure(closure)
}
def modules(Closure closure){
modules.configure(closure)
}
@Override
String toString() {
return "isDebug: $debugEnable\n" +
"apps: ${apps.isEmpty()? "is empty" : "$apps"}"+
"modules: ${modules.isEmpty()? "is empty" : "$modules"}"
}
}
AppExt 这个类是宿主App
class AppExt extends ModulesExt{
String dependMethod = "implementation" //默认依赖的方式
List<String> modules = new ArrayList<>() //依赖组件的名称集合
AppExt(String name) {
super(name)
}
......
各个组件的属性配置
class LibraryExt extends ModulesExt {
boolean isRunAlone = false //是否独立运行
String runAloneSuper //该moudle所依赖的子模块
LibraryExt(String name) {
super(name)
}
........
宿主App和各个组件的基类
class ModulesExt {
String name //组件名称
String applicationId //组件applicationId
String mainActivity //该组件启动的activity
ModulesExt(String name){
this.name = name //如果有父节点必须写构造方法
}
}
先用AppConfigPlugin处理整个项目项目build.gradle的配置信息
public class AppConfigPlugin implements Plugin<Project> {
private static final String EXTENSION_NAME = "appConfig"
@Override
void apply(Project project) {
AppConfigExt appConfigExt = new AppConfigExt(project)
// project.extensions,本扩展类其实就是只用于set、get的JavaBean类
project.extensions.add(EXTENSION_NAME, appConfigExt)
configApp(project)
}
void configApp(Project project) {
List<String> moduleList = new ArrayList<>()
NamedDomainObjectContainer<AppExt> appList
AppConfigExt appConfigExt
//当前project配置状态进行回调afterEvaluate(project开始配置前调用)
//和beforeEvaluate,afterEvaluate(project配置完成后回调)
project.afterEvaluate {
//得到配置的信息bean
appConfigExt = project.extensions.getByName(EXTENSION_NAME) as AppConfigExt
appList = appConfigExt.apps
checkRepeat(appConfigExt)
checkModules(appConfigExt,moduleList)
}
initChildModules(moduleList, project)
println("project child modules: $moduleList")
}
//运行后获取项目存在的所有组件
void initChildModules(List<String> moduleList ,Project project){
if (project.childProjects.isEmpty()){
moduleList.add(project.toString()
.replace("project ","")
.replace('\'',''))
return
}
//运行时遍历获取所有存在的组件(App壳工程和寄生组件)
project.childProjects.entrySet().forEach{
initChildModules(moduleList, it.value)
}
}
//检查配置的模块是否重复
static void checkRepeat(AppConfigExt appConfigExt){
//取出App壳工程组件名称列表 以name字段为分组条件
//很重要的一点是NamedDomainObjectContainer容器节点的名称是唯一的,是按照节点的名称排序的,
//如果有相同的节点名称 后面会覆盖前面的 (这个需要自行实验才能理解)
//并且必须有一个public的构造函数,接受string作为一个参数,必须有一个叫做name 的property
//这个property默认就是节点的名称 需要和项目路径对应,如果不填写默认为该配置的名字
//就是moudle节点的名称(如配置名为app的话,name则为:app)
//倒入规则和setting.gradle中的include规则保持一致
Map<String,List<AppExt>> appGroupMap =
appConfigExt.apps.groupBy{ it.name.startsWith(':') ? it.name : new String(":" + it.name)}
//k指的是name v指List<AppExt> 如果多个moudle节点的名称不同 但是里面的内容一样
//当v.size() > 1的时候说明apps组件名称重复
appGroupMap.forEach{
k,v ->
if (v.size() > 1){
throw new IllegalArgumentException("app is repeat. app name: [$k]")
}
}
//取出寄生组件名称列表
Map<String,List<LibraryExt>> moduleGroupMap =
appConfigExt.modules.groupBy{ it.name.startsWith(':') ? it.name : new String(":" + it.name)}
//k指的是name v指List<LibraryExt> 如果多个moudle节点的名称不同 但是里面的内容一样
//这个时候就表示v.size() > 1说明apps宿主名称名称重复
moduleGroupMap.forEach{
k,v ->
if (v.size() > 1){
throw new IllegalArgumentException("modules is repeat. modules name: [$k]")
}
}
}
//检查配置的模块名称是否合理正确
static void checkModules(AppConfigExt appConfigExt,
List<String> projectModules){
Set<String> configSet = new HashSet<>() //配置的所有组件集合
Set<String> modulesSet = new HashSet<>() //真实获取的组件集合
if (projectModules != null){
modulesSet.addAll(projectModules)
}
List<String> notFoundList = new ArrayList<>()
List<String> appNameList = appConfigExt.apps
.stream()
.map{it.name.startsWith(':') ? it.name : new String(":" + it.name)}.collect()
List<String> moduleNameList =
appConfigExt.modules.
stream().
map{
String name = it.name.startsWith(':') ? it.name : new String(":" + it.name)
//App壳工程的名字不能出现在其他组件当中
if (appNameList.contains(name)){
throw new IllegalArgumentException("$it.name already configured " +
"as an application, please check appConfig")
}
name
}.
collect()
println "moduleNameList = $moduleNameList"
configSet.addAll(appNameList)
configSet.addAll(moduleNameList)
configSet.forEach{
if(!modulesSet.contains(it)){
notFoundList.add(it)
}
}
//寄生组件配置的组件名称不存在
if (notFoundList.size() > 0){
throw new IllegalArgumentException(
"not fount modules = " + notFoundList
)
}
//App壳工程依赖的组件不存在
appConfigExt.apps.stream().forEach{ app ->
app.modules.stream().forEach{
if (! configSet.contains(it)){
throw new IllegalArgumentException(
"appConfig error , can not find $app.name modules $it by project" )
}
}
}
println("modules: " + configSet)
}
}
然后处理每个moudle的build.gradle的配置信息的配置信息
public class ModulesConfigPlugin implements Plugin<Project> {
private static final String PARENT_EXTENSION_NAME = "appConfig"
@Override
void apply(Project project) {
AppConfigExt appConfigExt = getAppConfigExtension(project)
configModules(project, appConfigExt)
}
static void configModules(Project project, AppConfigExt appConfigExt){
if (appConfigExt == null){
throw new NullPointerException("can not find appConfig")
}
List<AppExt> filterList = appConfigExt.apps.stream()
.filter{ (it.name.startsWith(':') ? it.name : new String(":" + it.name)).endsWith(project.name) }
.skip(0).collect() //Java8 Stream 语法
if (filterList != null && filterList.size() > 0){ //说明当前是已App壳工程编译
AppExt appExt = filterList.get(0)
AppPlugin appPlugin = project.plugins.apply(AppPlugin)
//App壳工程的build.gradle文件设置ApplicationId
appPlugin.extension.defaultConfig.setApplicationId(appExt.applicationId)
//检查配置app壳工程的Manifest文件信息
new AppManifestStrategy(project).resetManifest(appExt)
//检查配置app壳工程的依赖问题
dependModules(project, appExt, appConfigExt)
}else {
//检查配置子moudle独立运行
modulesRunAlone(project,appConfigExt.modules, appConfigExt.isDebug)
}
}
//app壳工程依赖
static void dependModules(Project project, AppExt appExt, AppConfigExt appConfigExt){
//遍历appConfigExt.modules数据和appExt.modules数据对比得到交集然后以map形式返回
Map<String,LibraryExt> moduleExtMap = appConfigExt.modules.stream().filter{
modules ->
String modulesName = appExt.modules.stream().find{ it.contains(modules.name) }
modulesName != null && !modulesName.isEmpty()
}.collect(Collectors.toMap({ it.name},{ it -> it}))
if (appExt.modules != null && appExt.modules.size() > 0){
List<String> modulesList = appExt.modules.stream()
.filter{ //遍历数据并检查其中的元素时使用 类似if
//是否开启debug模式 debug为true的时候 moudle的isRunAlone必须要是true才能被依赖
appConfigExt.isDebug ? (moduleExtMap != null && !moduleExtMap[it].isRunAlone) : true }
.map{ //map生成的是个一对一映射,for的作用
//添加依赖 implementation XXXX模块
project.dependencies.add(appExt.dependMethod, project.project(it))
it
}.collect()
println("build app: [$appExt.name] , depend modules: $modulesList")
}
}
//获取父节点的配置信息
AppConfigExt getAppConfigExtension(Project project){
try{
//项目根节点下的配置信息
return project.parent.extensions.getByName(PARENT_EXTENSION_NAME) as AppConfigExt
}catch (UnknownDomainObjectException ignored){
if (project.parent != null){
getAppConfigExtension(project.parent)
}else {
throw new UnknownDomainObjectException(ignored as String)
}
}
}
//子moudle独立运行
private static void modulesRunAlone(Project project, NamedDomainObjectContainer<LibraryExt> modules, boolean isDebug){
List<LibraryExt> filterList = modules.stream().filter{ it.name.endsWith(project.name) }.skip(0).collect()
if (filterList != null && filterList.size() > 0){//当前子moudle编译
LibraryExt moduleExt = filterList.get(0)
if (isDebug && moduleExt.isRunAlone){//开启debug模式并且isRunAlone为true
AppPlugin appPlugin = project.plugins.apply(AppPlugin)
appPlugin.extension.defaultConfig.setApplicationId(moduleExt.applicationId) //设置App工程ApplicationId
if (moduleExt.runAloneSuper != null && !moduleExt.runAloneSuper.isEmpty()){ //
project.dependencies.add("implementation", project.project(moduleExt.runAloneSuper))
println("build run alone modules: [$moduleExt.name], runSuper = $moduleExt.runAloneSuper")
}else{
println("build run alone modules: [$moduleExt.name]")
}
if (moduleExt.mainActivity != null && !moduleExt.mainActivity.isEmpty()){
//检查配置app壳工程的Manifest文件信息
new AppManifestStrategy(project).resetManifest(moduleExt)
}
}else{
project.plugins.apply(LibraryPlugin)//设置App依赖工程
//检查配置依赖工程的Manifest文件信息
new LibraryManifestStrategy(project).resetManifest(moduleExt)
}
}
}
}
处理每个moudle的时候需要处理每个moudle的AndroidManifest文件信息
abstract class ManifestStrategy {
protected String path
protected GPathResult manifest
boolean edit = false //AndroidManifest配置文件是否修改过
ManifestStrategy(Project project) {
path = "${project.getBuildFile().getParent()}/src/main/AndroidManifest.xml"
File manifestFile = new File(path) //找到moudle下的AndroidManifest.xml文件
if (!manifestFile.getParentFile().exists() && !manifestFile.getParentFile().mkdirs()) {
println "Unable to find AndroidManifest and create fail, please manually create"
}
manifest = new XmlSlurper().parse(manifestFile)//xml解析
}
//子类需要继承 设置启动Activity的IntentFilter属性
abstract void setMainIntentFilter(def activity, boolean isFindMain)
void resetManifest(ModulesExt moduleExt) {
if ([email protected] != moduleExt.applicationId && moduleExt.applicationId != null &&
!moduleExt.applicationId.isEmpty()) {
//重新设置manifest的包名(如果设置了 没用默认的applicationId)
[email protected] = moduleExt.applicationId
edit = true
}
boolean isFindMain = false //是否找到了启动activity
if (moduleExt.mainActivity != null && !moduleExt.mainActivity.isEmpty()) {
manifest.application.activity.each { activity ->
if ([email protected]'android:name' == moduleExt.mainActivity) {
def filter = activity.'intent-filter'.find {
[email protected]'android:name' == "android.intent.action.MAIN"
}
isFindMain = true
//如果没有设置IntentFilter条件 代码帮助其设置
setMainIntentFilter(activity, filter != null && filter.size() > 0)
}
}
}
manifest.application.activity.each { activity ->
def filter = activity.'intent-filter'.find {
[email protected]'android:name' == "android.intent.action.MAIN"
}
if (filter != null
&& moduleExt.mainActivity != null
&& !moduleExt.mainActivity.isEmpty()
&& [email protected]'android:name' != moduleExt.mainActivity) {
//如果设置了IntentFilter条件 但是启动activity和配置的不一样则清空IntentFilter条件
filter.replaceNode {}
edit = true
}
}
//如果在AndroidManifest配置文件当中没有找到启动activity那么就手动设置
if (!isFindMain) {
addMainActivity(manifest.application, moduleExt)
}
if (edit) {
buildModulesManifest(manifest)
}
}
//代码添加启动activity
void addMainActivity(def application, ModulesExt modulesExt) {
if (modulesExt.mainActivity != null && !modulesExt.mainActivity.isEmpty()) {
application.appendNode {
activity('android:name': modulesExt.mainActivity) {
'intent-filter' {
action('android:name': "android.intent.action.MAIN")
category('android:name': "android.intent.category.LAUNCHER")
}
}
}
edit = true
}
}
//动态修改AndroidManifest节点下的配置问题
void buildModulesManifest(def manifest) {
def fileText = new File(path)
StreamingMarkupBuilder outputBuilder = new StreamingMarkupBuilder() //创建XML
def root = outputBuilder.bind {
mkp.xmlDeclaration()
mkp.declareNamespace('android': 'http://schemas.android.com/apk/res/android')
mkp.yield manifest
}
String result = XmlUtil.serialize(root)
fileText.text = result
}
}
//App壳工程的AndroidManifest配置文件 setMainIntentFilter中添加intent-filter
class AppManifestStrategy extends ManifestStrategy {
AppManifestStrategy(Project project) {
super(project)
}
@Override
void setMainIntentFilter(def activity, boolean isFindMain) {
if (!isFindMain) {
activity.appendNode {
'intent-filter' {
action('android:name': "android.intent.action.MAIN")
category('android:name': "android.intent.category.LAUNCHER")
}
}
edit = true
}
}
}
//依赖moudle工程的AndroidManifest配置文件 setMainIntentFilter中清除intent-filter
class LibraryManifestStrategy extends ManifestStrategy {
LibraryManifestStrategy(Project project) {
super(project)
}
@Override
void setMainIntentFilter(def activity, boolean isFindMain) {
if (isFindMain) {
println "build lib"
activity.'intent-filter'.each {
if ([email protected]'android:name' == "android.intent.action.MAIN") {
it.replaceNode {}
edit = true
}
}
}
}
}
以上的代码注释信息便是这个项目的所有代码模块解析了,只有去阅读原作者相关文章和demo跑跑才能理解透彻。至于组件化全局的Application处理和组件通信这些方面的问题本文就不做深入探讨了。具体用法请查看相关说明calces-gradle-plugin。