1、创建plugin工程
2、自定义plugin实现Plugin接口
3、在apply(Project)主入口方法内,完成插件的加载
1、定义层级关系,从上往下依次为app/component/library/base,
2、定位单个组件的类型定义等级,通过等级来控制是否可用做依赖
例:将工程区分为api和impl模块,组件之间通信,只能依赖api模块
3、自定义插件,如上
4、主工程壳里面,实现组件的加载,
具体实现代码分为:
1、统一配置config.gradle
ext {
/**
* 普通变量,统一的路径表
*
* libPath:jar包和aar包放置的目录,方便填写引用依赖库的文件
*/
paths = [
libPath: "${rootProject.projectDir}/base/common/libs"
]
/**
* 插件变量,允许被依赖的模块目录,若需消除此约束,则删掉变量。
*
* 1、表示m_orderDirectory中目录内的模块,可以被其它模块所依赖,违反此约束将抛出异常;
*
* 2、目录的下标越大,表示层级越低,层级高的模块可依赖层级低的,不可反向依赖,否则会抛出异常。
*
* 例如:
* component目录中带_aip后缀的模块可依赖library目录中带_aip后缀以及base的,但反过来不行
*/
m_top_category = ['app', 'component', 'library', 'base']
/**
* 公共库列表,例如:base
*/
m_common_directory = ['base']
/**
* 组件依赖有效后缀,例如:"_api"
*/
m_api_suffix = ['_api']
/**
* 插件调试开关
*/
m_debug_mode = false
/**
* 插件变量,依赖库信息。
*/
m_dependency = [
]
m_dependency_ex = [
]
}
1、自定义plugin
import org.apache.commons.io.FileUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
class ModularizationPlugin implements Plugin<Project> {
public static final String PLUGIN_NAME = 'modularization'
@Override
void apply(Project project) {
println "project(${project.name}) apply ${PLUGIN_NAME} plugin"
applyPluginConfiguration(project)
def isApp = ProjectModuleManager.manageModule(project)
performBuildTypeCache(project, isApp)
}
private static void applyPluginConfiguration(Project project) {
String configPath = "${project.rootProject.projectDir}/config.gradle"
if (project.file(configPath).exists()) {
project.apply from: configPath
}
}
private static void performBuildTypeCache(Project project, boolean isApp) {
if (!RegisterCache.isSameAsLastBuildType(project, isApp)) {
RegisterCache.cacheBuildType(project, isApp)
//切换app/lib编译时,将transform目录清除
def cachedJniFile = project.file("build/intermediates/transforms/")
if (cachedJniFile && cachedJniFile.exists() && cachedJniFile.isDirectory()) {
FileUtils.deleteDirectory(cachedJniFile)
}
}
}
}
2、逻辑实现部分
import org.apache.http.util.TextUtils
import org.gradle.api.Project
import java.util.regex.Pattern
class ProjectModuleManager {
//插件名称
static final String PLUGIN_NAME = ModularizationPlugin.PLUGIN_NAME
//组件单独以app方式运行时使用的测试代码所在目录(manifest/java/assets/res等),这个目录下的文件不会打包进主app
static final String DEBUG_DIR = "src/main/debug/"
//依赖库
static final String CONFIG_DEPENDENCY = "m_dependency"
//顶级目录
static final String TOP_CATEGORY = "m_top_category"
//公共module的根目录
static final String BASE_DIRECTORY = "m_common_directory"
//支持的后缀
static final String API_SUFFIX = "m_api_suffix"
//是否为插件debug模式
static final String DEBUG_MODE = "m_debug_mode"
//主app,一直以application方式编译
static final String MAIN_APP = "m_main_app"
//以application方式编译的模块
static final String MODULE_APP = "m_module.app"
// 是否集成测试
static final String INTEGRATION_TEST = "m_integration.test"
//需要集成打包相关的task
static final String TASK_TYPES = ".*((((ASSEMBLE)|(BUILD)|(INSTALL)|((BUILD)?TINKER)|(RESGUARD)).*)|(ASR)|(ASD))"
static final MODULE_TYPE_API = 1
static final MODULE_TYPE_BASE = 2
static String sAssembleModule
static boolean sAssembleTask
static boolean sMainApp
static boolean sModuleApp
static Properties sLocalProperties
static Map<String, MyProjectInfo> sProjectCategoryMap
static boolean sDebug
static boolean sIntegrationTest
static boolean manageModule(Project project) {
sAssembleTask = false
sAssembleModule = null
sMainApp = false
sModuleApp = false
sDebug = project.ext.has(DEBUG_MODE) && project.ext.m_debug_mode
checkDependencies(project)
checkTaskAssemble(project)
initProjectCategoryMap(project)
loadLocalProperties(project)
sMainApp = isMainApp(project)
sIntegrationTest = isIntegrationTest()
sModuleApp = isModuleApp(project)
boolean runAsApp = false
if (sMainApp) {
runAsApp = true
} else if (sModuleApp && (!sAssembleTask || project.name == sAssembleModule)) {
runAsApp = true
}
project.ext.runAsApp = runAsApp
println "${PLUGIN_NAME}: project=${project.name}, integrationTest=${sIntegrationTest}, runAsApp=${runAsApp}, sAssembleTask=${sAssembleTask}, mainApp=${sMainApp}, moduleApp=${sModuleApp}, moduleAppName=${sAssembleModule}"
if (sIntegrationTest) {
if (sMainApp) {
project.apply plugin: 'com.android.application'
} else {
project.apply plugin: 'com.android.library'
}
addProjectSourceSet(project)
addResourcePrefix(project)
} else if (runAsApp) {
project.apply plugin: 'com.android.application'
if (!sMainApp) {
addProjectSourceSet(project)
addResourcePrefix(project)
}
} else {
project.apply plugin: 'com.android.library'
}
addComponentDependencyMethod(project)
return runAsApp
}
private static void checkDependencies(Project project) {
if (project.ext.has(CONFIG_DEPENDENCY)) {
def depList = []
project.ext.m_dependency.each { entry ->
if (TextUtils.isBlank(entry.key) || TextUtils.isBlank(entry.value)) {
throw new IllegalArgumentException("Illegal dependency format : ${entry}")
}
String key = entry.value
if (!entry.value.contains("/") && !entry.value.contains("\\")) {
//如果是远程依赖,获取groupId和artifactId
int index = entry.value.lastIndexOf(":")
if (index < 0) {
throw new IllegalArgumentException("Illegal dependency value : ${entry.value}")
}
key = entry.value.substring(0, index)
}
if (TextUtils.isBlank(key)) {
throw new IllegalArgumentException("Illegal dependency value : ${entry.value}")
}
//判断是否有重复引用依赖库的情况
if (depList.contains(key)) {
throw new IllegalArgumentException("Duplicated dependency value ${key} for ${entry.value}")
}
depList.add(key)
}
}
}
static void initProjectCategoryMap(Project project) {
if (project.ext.has(TOP_CATEGORY)) {
Set<Project> allProjects = project.rootProject.getAllprojects()
sProjectCategoryMap = new LinkedHashMap<>()
for (Project p : allProjects) {
if (TextUtils.isEmpty(p.path)) {
continue
}
String[] splitPaths = p.path.split(":")
if (splitPaths == null || splitPaths.length <= 1) {
continue
}
String categoryName = splitPaths[1]
if (sDebug) {
println("project : ${p.path}, categoryName : ${categoryName}")
}
if (project.ext.m_top_category.indexOf(categoryName) >= 0) {
MyProjectInfo projectInfo = new MyProjectInfo()
projectInfo.categoryName = categoryName
projectInfo.leafModule = p.subprojects == null || p.subprojects.isEmpty()
if (isApiModule(project, p.name)) {
projectInfo.moduleType = MODULE_TYPE_API
} else if (isBaseModule(project, categoryName)) {
projectInfo.moduleType = MODULE_TYPE_BASE
}
sProjectCategoryMap.put(p.path, projectInfo)
}
}
}
}
/**
* 加载本地local.properties的键值对
*/
static void loadLocalProperties(Project project) {
sLocalProperties = new Properties()
try {
def localFile = project.rootProject.file('local.properties')
if (localFile != null && localFile.exists()) {
sLocalProperties.load(localFile.newDataInputStream())
}
} catch (Exception ignored) {
println("${PLUGIN_NAME}: local.properties not found")
}
}
/**
* 判断当前的task是否为集成打包相关的task
*/
static void checkTaskAssemble(Project project) {
def taskNames = project.gradle.startParameter.taskNames
def allModuleBuildApkPattern = Pattern.compile(TASK_TYPES)
for (String task : taskNames) {
if (allModuleBuildApkPattern.matcher(task.toUpperCase()).matches()) {
sAssembleTask = true
if (task.contains(":")) {
def arr = task.split(":")
sAssembleModule = arr[arr.length - 2].trim()
}
return
}
}
}
/**
* 判断当前Project是否为主app,即一直以application方式编译
*/
static boolean isMainApp(Project project) {
return project.ext.has(MAIN_APP)
}
/**
* 判断当前Project是否以application方式编译的模块
*/
static boolean isModuleApp(Project project) {
return sLocalProperties != null && project.name == sLocalProperties.getProperty(MODULE_APP)
}
static boolean isIntegrationTest() {
return sLocalProperties != null && sLocalProperties.getProperty(INTEGRATION_TEST) == "true"
}
/**
* 以application方式编译的模块,代码以及资源需添加debug目录
*/
static void addProjectSourceSet(Project project) {
project.android.sourceSets.main {
//application模式下,如果存在src/main/debug/AndroidManifest.xml,则自动使用其作为manifest文件
def debugManifest = "${DEBUG_DIR}AndroidManifest.xml"
if (project.file(debugManifest).exists()) {
manifest.srcFile debugManifest
}
//application模式下,如果存在src/main/debug/assets,则自动将其添加到assets源码目录
if (project.file("${DEBUG_DIR}assets").exists()) {
assets.srcDirs += "${DEBUG_DIR}assets"
}
//application模式下,如果存在src/main/debug/java,则自动将其添加到java源码目录
if (project.file("${DEBUG_DIR}java").exists()) {
java.srcDirs += "${DEBUG_DIR}java"
}
//application模式下,如果存在src/main/debug/res,则自动将其添加到资源目录
if (project.file("${DEBUG_DIR}res").exists()) {
res.srcDirs += "${DEBUG_DIR}res"
}
//application模式下,如果存在src/main/debug/aidl,则自动将其添加到aidl目
if (project.file("${DEBUG_DIR}aidl").exists()) {
aidl.srcDirs += "${DEBUG_DIR}aidl"
}
if (project.file("${DEBUG_DIR}jni").exists()) {
jni.srcDirs += "${DEBUG_DIR}jni"
}
if (project.file("${DEBUG_DIR}jniLibs").exists()) {
jniLibs.srcDirs += "${DEBUG_DIR}jniLibs"
}
if (project.file("${DEBUG_DIR}renderscript").exists()) {
renderscript.srcDirs += "${DEBUG_DIR}renderscript"
}
if (project.file("${DEBUG_DIR}resources").exists()) {
resources.srcDirs += "${DEBUG_DIR}resources"
}
}
}
/**
* 防止模块间的资源同名,导致资源冲突,所以强制资源命名时需以模块名作为资源前缀,从而保证资源命名的唯一性
*/
static void addResourcePrefix(Project project) {
if (!sMainApp) {
project.android {
resourcePrefix "${project.name}_"
}
}
}
static boolean isApiModule(Project project, String moduleName) {
if (project.ext.has(API_SUFFIX)) {
for (String supportedSuffix : project.ext.m_api_suffix) {
if (moduleName.endsWith(supportedSuffix)) {
return true
}
}
}
return false
}
static boolean isBaseModule(Project project, String categoryName) {
if (project.ext.has(BASE_DIRECTORY)) {
for (String supportedRootName : project.ext.m_common_directory) {
if (supportedRootName == categoryName) {
return true
}
}
}
return false
}
static void addComponentDependencyMethod(Project project) {
/**
* implementModule 模块直接依赖,只能依赖于api模块
* implementLib、compileOnlyLib、annotationProcessorLib 库依赖
*
* 上述依赖均包含以下两种情形:
* 1、testIncluded为true,说明该lib是在带测试界面时才依赖,即sIntegrationTest为true的时候才需要依赖
* 2、testIncluded为false,正常依赖
*/
def setCommonDependency = { dependencyPattern, moduleName, testIncluded = false ->
if (sIntegrationTest && testIncluded || !testIncluded) {
if (sDebug) {
println("addComponentDependencyMethod: dependencyPattern = " + dependencyPattern + ", moduleName = " + moduleName + ", sIntegrationTest = " + sIntegrationTest + ", testIncluded = " + testIncluded)
}
for (Project p : project.getRootProject().getSubprojects()) {
if (moduleName == p.getName()) {
MyProjectInfo projectInfo = sProjectCategoryMap.get(p.getPath())
if (projectInfo == null) {
throw new IllegalArgumentException(p.getName() + " not included, check it.")
}
String rootName = projectInfo.categoryName
if (sDebug) {
println("addComponentDependencyMethod: moduleName = " + moduleName + ", rootName = " + rootName + ", project.name = " + project.name)
}
if (!TextUtils.isEmpty(rootName)) {
// step1.依赖的module有效性校验;被依赖的module不包含已支持的后缀,如api;且不属于已支持的公共库如base,则抛出异常,使得编译不通过
boolean isSupportedSuffix = isApiModule(project, moduleName)
boolean isSupportedCommonDirectory = isBaseModule(project, rootName)
if (!isSupportedSuffix && !isSupportedCommonDirectory) {
throw new IllegalArgumentException(moduleName + " must in directory of base or is api module.")
}
MyProjectInfo currentProjectInfo = sProjectCategoryMap.get(project.getPath())
if (currentProjectInfo == null) {
throw new IllegalArgumentException(moduleName + " not included, check it.")
}
String currentRootName = currentProjectInfo.categoryName
int moduleIndex = project.ext.m_top_category.indexOf(rootName)
int currentIndex = project.ext.m_top_category.indexOf(currentRootName)
if (sDebug) {
println("addComponentDependencyMethod: moduleIndex = " + moduleIndex + ", currentIndex = " + currentIndex + ", currentRootName = " + currentRootName + ", project.getPath() = " + project.getPath())
}
// step2.范围校验,module不在指定范围,抛出异常,使得编译不通过
if (moduleIndex == -1 || currentIndex == -1) {
throw new IllegalArgumentException("implementModule of \'" + moduleName + "\' should be the child directory of m_top_category.")
}
// step3.层级依赖关系校验,m_top_category中低下标可依赖高下标,反之编译不通过
if (moduleIndex < currentIndex) {
throw new IllegalArgumentException("Can't implement upper-level api module : \"${moduleName}\" in \"${project.getName()}\"")
}
}
project.dependencies.add(dependencyPattern, p)
return
}
}
}
}
project.ext.implementModule = { moduleName, testIncluded = false ->
setCommonDependency("implementation", moduleName, testIncluded)
}
project.ext.compileOnlyModule = { moduleName, testIncluded = false ->
setCommonDependency("compileOnly", moduleName, testIncluded)
}
project.ext.implementLib = { libName, testIncluded = false, excludeParams = null ->
if (sIntegrationTest && testIncluded || !testIncluded) {
if (sDebug) {
println("implementLib: libName = " + libName + ", sIntegrationTest = " + sIntegrationTest + ", testIncluded " + testIncluded)
}
if (!project.ext.has(CONFIG_DEPENDENCY) || !project.ext.m_dependency.containsKey(libName)) {
throw new IllegalArgumentException("Cant find implementation libPath for \"${libName}\"")
}
String libPath = project.ext.m_dependency[libName]
if (libPath.contains("/") || libPath.contains("\\")) {
project.dependencies.add("implementation", project.files(libPath))
} else {
if (excludeParams == null) {
project.dependencies.add("implementation", libPath)
} else {
project.dependencies.add("implementation", libPath, {
exclude excludeParams
})
}
}
}
}
project.ext.compileOnlyLib = { libName, testIncluded = false ->
if (sIntegrationTest && testIncluded || !testIncluded) {
if (!project.ext.has(CONFIG_DEPENDENCY) || !project.ext.m_dependency.containsKey(libName)) {
throw new IllegalArgumentException("Cant find compileOnly libPath for \"${libName}\"")
}
String libPath = project.ext.m_dependency[libName]
if (libPath.contains("/") || libPath.contains("\\")) {
project.dependencies.add("compileOnly", project.files(libPath))
} else {
project.dependencies.add("compileOnly", libPath)
}
}
}
project.ext.annotationProcessorLib = { libName, testIncluded = false ->
if (sIntegrationTest && testIncluded || !testIncluded) {
if (!project.ext.has(CONFIG_DEPENDENCY) || !project.ext.m_dependency.containsKey(libName)) {
throw new IllegalArgumentException("Cant find annotationProcessor libPath for \"${libName}\"")
}
String libPath = project.ext.m_dependency[libName]
if (libPath.contains("/") || libPath.contains("\\")) {
project.dependencies.add("annotationProcessor", project.files(libPath))
} else {
project.dependencies.add("annotationProcessor", libPath)
}
}
}
//判断task是否为给本module打apk包
def curModuleIsBuildingApk = sAssembleTask && (sMainApp || sAssembleModule == project.name)
/**
* runtimeModule 模块间接依赖,没有直接的代码引用,打包时编到apk中
*
* 该依赖包含以下三种情形:
* 1、说明不是在处于打包apk的task,这种情况不引用直接返回,从而达到代码隔离的效果
* 2、testIncluded为true,说明该lib是在带测试界面时才依赖,即sIntegrationTest为true的时候才需要依赖
* 3、testIncluded为false,正常依赖
*/
project.ext.runtimeModule = { moduleName, testIncluded = false ->
if (!curModuleIsBuildingApk) {
return
}
if (sIntegrationTest && testIncluded || !testIncluded) {
for (Project p : project.getRootProject().getSubprojects()) {
if (moduleName == p.getName()) {
project.dependencies.implementation p
return
}
}
}
}
}
}
自定义工程实体类
class MyProjectInfo {
private String categoryName
private boolean leafModule
private int moduleType
}