概述
关于replugin介绍及简单使用可以参照官方文档,在这里不再累述。本文之前需要对自定义gradle插件有一定基础以方便理解,参见Gradle学习-----Gradle自定义插件。
NOTE:文中会提及两种插件,请阅读本文时注意提及插件的上下文情景,避免混淆概念
1:replugin插件:即replugin插件化框架所指的插件,这个插件指android应用业务拆分出的独立模块,是android应用或模块。
2:gradle插件(本文及上文链接重点):即gradle构建所需的构建插件,是gradle应用或模块。
目录概览
\qihoo\RePlugin\replugin-host-gradle\src
│
└─main
├─groovy
│ └─com
│ └─qihoo360
│ └─replugin
│ └─gradle
│ └─host
│ │ AppConstant.groovy # 程序常量定义类
│ │ RePlugin.groovy <====== (入口) # 针对宿主的特定构建任务创建及调度
│ │
│ ├─creator
│ │ │ FileCreators.groovy # 组装生成器
│ │ │ IFileCreator.groovy # 文件生成器接口
│ │ │
│ │ └─impl
│ │ ├─java
│ │ │ RePluginHostConfigCreator.groovy # RePluginHostConfig.java 生成器
│ │ │
│ │ └─json
│ │ PluginBuiltinJsonCreator.groovy # plugins-builtin.json 生成器
│ │ PluginInfo.groovy # 插件信息模型
│ │ PluginInfoParser.groovy # 从 manifest 的 xml 中抽取 PluginInfo信息
│ │
│ └─handlemanifest
│ ComponentsGenerator.groovy # 动态生成插件化框架中需要的组件
│
└─resources
└─META-INF
└─gradle-plugins
replugin-host-gradle.properties # 指定 gradle 插件实现类
分解
1:预生成AndroidManifest.xml中的组件坑位
@Override
public void apply(Project project) {
println "${TAG} Welcome to replugin world ! "
this.project = project
/* Extensions */
project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig) //读取gradle配置中的repluginHostConfig配置
if (project.plugins.hasPlugin(AppPlugin)) {
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
addShowPluginTask(variant)
if (config == null) {
config = project.extensions.getByName(AppConstant.USER_CONFIG)
checkUserConfig(config)
}
def appID = variant.generateBuildConfig.appPackageName
println "${TAG} appID: ${appID}"
def newManifest = ComponentsGenerator.generateComponent(appID, config)
...
}
}
}
- 首先,将RepluginConfig类的常量配置信息赋值给AppConstant.USER_CONFIG(即repluginHostConfig)配置,在接下来
checkUserConfig(config)
用到。当如果我们不配置,则默认配置如下
class RepluginConfig {
/** 自定义进程的数量(除 UI 和 Persistent 进程) */
def countProcess = 3
/** 是否使用常驻进程? */
def persistentEnable = true
/** 常驻进程名称(也就是上面说的 Persistent 进程,开发者可自定义)*/
def persistentName = ':GuardService'
/** 背景不透明的坑的数量 */
def countNotTranslucentStandard = 6
def countNotTranslucentSingleTop = 2
def countNotTranslucentSingleTask = 3
def countNotTranslucentSingleInstance = 2
/** 背景透明的坑的数量 */
def countTranslucentStandard = 2
def countTranslucentSingleTop = 2
def countTranslucentSingleTask = 2
def countTranslucentSingleInstance = 3
/** 宿主中声明的 TaskAffinity 的组数 */
def countTask = 2
/**
* 是否使用 AppCompat 库
* com.android.support:appcompat-v7:25.2.0
*/
def useAppCompat = false
/** HOST 向下兼容的插件版本 */
def compatibleVersion = 10
/** HOST 插件版本 */
def currentVersion = 12
/** plugins-builtin.json 文件名自定义,默认是 "plugins-builtin.json" */
def builtInJsonFileName = "plugins-builtin.json"
/** 是否自动管理 plugins-builtin.json 文件,默认自动管理 */
def autoManageBuiltInJsonFile = true
/** assert目录下放置插件文件的目录自定义,默认是 assert 的 "plugins" */
def pluginDir = "plugins"
/** 插件文件的后缀自定义,默认是".jar" 暂时支持 jar 格式*/
def pluginFilePostfix = ".jar"
/** 当发现插件目录下面有不合法的插件 jar (有可能是特殊定制 jar)时是否停止构建,默认是 true */
def enablePluginFileIllegalStopBuild = true
}
判断project中是否含有AppPlugin类型插件,即我们在宿主项目中是应用了该类型插件,也就是是否在build.gradle中写了:apply plugin: 'com.android.application'.所以,我们在使用
apply plugin: 'replugin-host-gradle'
时,必须在apply plugin: 'com.android.application'
之后。
如果希望判断是否有libraryPlugin,可以这样写:if (project.plugins.hasPlugin(LibraryPlugin))
获取project中的AppExtension类型extension,即com.android.application projects的android extension.也就是在你的app模块的build.gradle中定义的闭包:
android { ...}
遍历android extension的Application variants 列表。这可以说是 Hook Android gradle 插件的一种方式,因为通过遍历applicationVariants,你可以修改属性,名字,描述,输出文件名等,如果是Android library库,那么就将applicationVariants替换为libraryVariants。我们在app的build.gradle中这样定义过闭包:
buildTypes {
release {
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
def fileName = "xxx_${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}
其实这也是一种插件的创建方式,Hook Android gradle 插件动态修改variants属性值,修改打包输出的apk文件名。
创建自定义gradle插件,Gradle提供了多种方式(Gradle学习-----Gradle自定义插件提过):
1:在build.gradle脚本中直接创建(上述代码即是)
2:在独立Module中创建(replugin-host-gradle即是)
- 调用
addShowPluginTask(variant)
方法,代码如下
// 添加 【查看所有插件信息】 任务
def addShowPluginTask(def variant) {
def variantData = variant.variantData
def scope = variantData.scope
def showPluginsTaskName = scope.getTaskName(AppConstant.TASK_SHOW_PLUGIN, "")
def showPluginsTask = project.task(showPluginsTaskName)
showPluginsTask.doLast {
IFileCreator creator = new PluginBuiltinJsonCreator(project, variant, config)
def dir = creator.getFileDir()
if (!dir.exists()) {
println "${AppConstant.TAG} The ${dir.absolutePath} does not exist "
println "${AppConstant.TAG} pluginsInfo=null"
return
}
String fileContent = creator.getFileContent()
if (null == fileContent) {
return
}
new File(dir, creator.getFileName()).write(fileContent, 'UTF-8')
}
showPluginsTask.group = AppConstant.TASKS_GROUP
//get mergeAssetsTask name
String mergeAssetsTaskName = variant.getVariantData().getScope().getMergeAssetsTask().name
//get real gradle task
def mergeAssetsTask = project.tasks.getByName(mergeAssetsTaskName)
//depend on mergeAssetsTask so that assets have been merged
if (mergeAssetsTask) {
showPluginsTask.dependsOn mergeAssetsTask
}
}
但是方法内指定的task并未挂到android gradle task上,即task不会执行。这个task是方便调试时查看插件信息的.
- 接下来调用
checkUserConfig
,源码如下:
/**
* 检查用户配置项
*/
def checkUserConfig(config) {
/*
def persistentName = config.persistentName
if (persistentName == null || persistentName.trim().equals("")) {
project.logger.log(LogLevel.ERROR, "\n---------------------------------------------------------------------------------")
project.logger.log(LogLevel.ERROR, " ERROR: persistentName can'te be empty, please set persistentName in replugin. ")
project.logger.log(LogLevel.ERROR, "---------------------------------------------------------------------------------\n")
System.exit(0)
return
}
*/
doCheckConfig("countProcess", config.countProcess)
doCheckConfig("countTranslucentStandard", config.countTranslucentStandard)
doCheckConfig("countTranslucentSingleTop", config.countTranslucentSingleTop)
doCheckConfig("countTranslucentSingleTask", config.countTranslucentSingleTask)
doCheckConfig("countTranslucentSingleInstance", config.countTranslucentSingleInstance)
doCheckConfig("countNotTranslucentStandard", config.countNotTranslucentStandard)
doCheckConfig("countNotTranslucentSingleTop", config.countNotTranslucentSingleTop)
doCheckConfig("countNotTranslucentSingleTask", config.countNotTranslucentSingleTask)
doCheckConfig("countNotTranslucentSingleInstance", config.countNotTranslucentSingleInstance)
doCheckConfig("countTask", config.countTask)
println '--------------------------------------------------------------------------'
// println "${TAG} appID=${appID}"
println "${TAG} useAppCompat=${config.useAppCompat}"
// println "${TAG} persistentName=${config.persistentName}"
println "${TAG} countProcess=${config.countProcess}"
println "${TAG} countTranslucentStandard=${config.countTranslucentStandard}"
println "${TAG} countTranslucentSingleTop=${config.countTranslucentSingleTop}"
println "${TAG} countTranslucentSingleTask=${config.countTranslucentSingleTask}"
println "${TAG} countTranslucentSingleInstance=${config.countTranslucentSingleInstance}"
println "${TAG} countNotTranslucentStandard=${config.countNotTranslucentStandard}"
println "${TAG} countNotTranslucentSingleTop=${config.countNotTranslucentSingleTop}"
println "${TAG} countNotTranslucentSingleTask=${config.countNotTranslucentSingleTask}"
println "${TAG} countNotTranslucentSingleInstance=${config.countNotTranslucentSingleInstance}"
println "${TAG} countTask=${config.countTask}"
println '--------------------------------------------------------------------------'
}
读取一开始加载进来的repluginHostConfig
配置信息做数据类型正确性校验。
- 关键代码,下面一行代码,搞定了宿主中AndroidManifest.xml中的组件坑位生成,注意,结合结构概览中的gradle Flow 看,这里只是生成组件坑位的xml代码,最终的xml文件是在后续的task中拼装出来的,稍后会讲到。
def newManifest = ComponentsGenerator.generateComponent(appID, config)
该方法源码如下:
def static generateComponent(def applicationID, def config) {
// 是否使用 AppCompat 库(涉及到默认主题)
if (config.useAppCompat) {
themeNTS = THEME_NTS_NOT_APP_COMPAT
} else {
themeNTS = THEME_NTS_NOT_USE_APP_COMPAT
}
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
/* UI 进程 */
xml.application {
/* 透明坑 */
config.countTranslucentStandard.times {
activity(
"${name}": "${applicationID}.${infix}N1NRTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}")
...
}
...
/* 不透明坑 */
config.countNotTranslucentStandard.times{
}
...
}
// 删除 application 标签
def normalStr = writer.toString().replace("<application>", "").replace("</application>", "")
// println "${TAG} normalStr: ${normalStr}"
// 将单进程和多进程的组件相加
normalStr + generateMultiProcessComponent(applicationID, config)
}
主要作用就是基于 Groovy 的 MarkupBuilder api,根据 RepluginConfig 类中的配置,拼出组件坑位的xml 字符串。就像搭积木一样,以其中一组作为研究。
config.countTranslucentStandard.times {
activity(
"${name}": "${applicationID}.${infix}N1NRTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}")
}
NOTE:config.countTranslucentStandard.times
含义:根据config.countTranslucentStandard
的值循环
生成的坑位:
<activity
android:theme="@ref/0x01030010"
android:name="com.qihoo360.replugin.sample.host.loader.a.ActivityN1NRTS0"
android:exported="false"
android:screenOrientation="1"
android:configChanges="0x4b0" />
即:替换
生成 RePluginHostConfig 配置文件
@Override
public void apply(Project project) {
...
if (project.plugins.hasPlugin(AppPlugin)) {
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
...
def variantData = variant.variantData
def scope = variantData.scope
//host generate task
def generateHostConfigTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "HostConfig")
def generateHostConfigTask = project.task(generateHostConfigTaskName)
generateHostConfigTask.doLast {
FileCreators.createHostConfig(project, variant, config)
}
generateHostConfigTask.group = AppConstant.TASKS_GROUP
//depends on build config task
String generateBuildConfigTaskName = variant.getVariantData().getScope().getGenerateBuildConfigTask().name
def generateBuildConfigTask = project.tasks.getByName(generateBuildConfigTaskName)
if (generateBuildConfigTask) {
generateHostConfigTask.dependsOn generateBuildConfigTask
generateBuildConfigTask.finalizedBy generateHostConfigTask
}
...
}
}
}
继续回到 apply 方法,接下来该到生成 RePluginHostConfig 的时候了,即 注释中的host generate task
- 首先生成了 HostConfig 的gradle task 名字,并调用project的task()方法创建此Task。
- 指定了 generateHostConfigTask 的task任务:自动创建RePluginHostConfig.java至BuildConfig目录。
generateHostConfigTask.doLast {
FileCreators.createHostConfig(project, variant, config)
}
注:createHostConfig(...)
也是根据配置类 RepluginConfig
中的配置信息拼装生成的java文件。
//depends on build config task
if (generateBuildConfigTask) {
generateHostConfigTask.dependsOn generateBuildConfigTask
generateBuildConfigTask.finalizedBy generateHostConfigTask
}
因为此task中创建的RePluginHostConfig.java希望放置到编译输出目录..\replugin-sample\host\app\build\generated\source\buildConfig\{productFlavors}\{buildTypes}\...
下,所以此task依赖于生成 BuildConfig.java 的task并设置为 BuildConfigTask 执行完后,就执行HostConfigTask。
关于gradle 的 task 相关知识,可以去gradle 官网或某搜索引擎查看学习,属于字典型知识点,需要时候查阅下。
生成 plugins-builtin.json 插件信息文件
@Override
public void apply(Project project) {
...
if (project.plugins.hasPlugin(AppPlugin)) {
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
...
//json generate task
def generateBuiltinJsonTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "BuiltinJson")
def generateBuiltinJsonTask = project.task(generateBuiltinJsonTaskName)
generateBuiltinJsonTask.doLast {
FileCreators.createBuiltinJson(project, variant, config)
}
generateBuiltinJsonTask.group = AppConstant.TASKS_GROUP
//depends on mergeAssets Task
String mergeAssetsTaskName = variant.getVariantData().getScope().getMergeAssetsTask().name
def mergeAssetsTask = project.tasks.getByName(mergeAssetsTaskName)
if (mergeAssetsTask) {
generateBuiltinJsonTask.dependsOn mergeAssetsTask
mergeAssetsTask.finalizedBy generateBuiltinJsonTask
}
...
}
}
}
继续回到 apply 方法,接下来该到生成 plugins-builtin.json 这个包含了插件信息的文件的时候了,即 注释中的json generate task
。
- 首先生成个gradle task 名字,并调用project的task()方法创建此Task。
- 指定了 generateBuiltinJsonTask 的task任务:扫描宿主\assets\plugins目录下的插件文件,并基于apk文件规则解析出插件信息,包名,版本号等,然后拼装成json文件。
generateBuiltinJsonTask.doLast {
FileCreators.createBuiltinJson(project, variant, config)
}
设置 generateBuiltinJsonTask 的执行依赖
//depends on build config task
if (mergeAssetsTask) {
generateBuiltinJsonTask.dependsOn mergeAssetsTask
mergeAssetsTask.finalizedBy generateBuiltinJsonTask
}
因为此task中创建的 plugins-builtin.json 希望放置到编译输出目录...\replugin-sample\host\app\build\intermediates\assets\{productFlavors}\{buildTypes}\...
下,所以此task依赖于merge assets文件 的task并设置为 mergeAssetsTask
执行完后,就执行BuiltinJsonTask
。
拼装 AndroidManifest.xml
output.processManifest.doLast {
def manifestPath = output.processManifest.outputFile.absolutePath
def updatedContent = new File(manifestPath).getText("UTF-8").replaceAll("</application>", newManifest + "</application>")
new File(manifestPath).write(updatedContent, 'UTF-8')
}
- 将坑位 xml 字符串 与 原有xml <application></application> 标签内的配置信息合二为一。
至此,replugin-host-gradle
插件的工作就全部结束了。