概述
關(guān)于replugin介紹及簡(jiǎn)單使用可以參照官方文檔,在這里不再累述驴剔。本文之前需要對(duì)自定義gradle插件有一定基礎(chǔ)以方便理解苗胀,參見Gradle學(xué)習(xí)-----Gradle自定義插件俐东。
NOTE:文中會(huì)提及兩種插件尘颓,請(qǐng)閱讀本文時(shí)注意提及插件的上下文情景兄春,避免混淆概念
1:replugin插件:即replugin插件化框架所指的插件疯趟,這個(gè)插件指android應(yīng)用業(yè)務(wù)拆分出的獨(dú)立模塊拘哨,是android應(yīng)用或模塊。
2:gradle插件(本文及上文鏈接重點(diǎn)):即gradle構(gòu)建所需的構(gòu)建插件信峻,是gradle應(yīng)用或模塊倦青。
目錄概覽
\qihoo\RePlugin\replugin-host-gradle\src
│
└─main
├─groovy
│ └─com
│ └─qihoo360
│ └─replugin
│ └─gradle
│ └─host
│ │ AppConstant.groovy # 程序常量定義類
│ │ RePlugin.groovy <====== (入口) # 針對(duì)宿主的特定構(gòu)建任務(wù)創(chuàng)建及調(diào)度
│ │
│ ├─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 # 動(dòng)態(tài)生成插件化框架中需要的組件
│
└─resources
└─META-INF
└─gradle-plugins
replugin-host-gradle.properties # 指定 gradle 插件實(shí)現(xiàn)類
分解
1:預(yù)生成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)配置,在接下來(lái)
checkUserConfig(config)
用到盹舞。當(dāng)如果我們不配置产镐,則默認(rèn)配置如下
class RepluginConfig {
/** 自定義進(jìn)程的數(shù)量(除 UI 和 Persistent 進(jìn)程) */
def countProcess = 3
/** 是否使用常駐進(jìn)程? */
def persistentEnable = true
/** 常駐進(jìn)程名稱(也就是上面說(shuō)的 Persistent 進(jìn)程踢步,開發(fā)者可自定義)*/
def persistentName = ':GuardService'
/** 背景不透明的坑的數(shù)量 */
def countNotTranslucentStandard = 6
def countNotTranslucentSingleTop = 2
def countNotTranslucentSingleTask = 3
def countNotTranslucentSingleInstance = 2
/** 背景透明的坑的數(shù)量 */
def countTranslucentStandard = 2
def countTranslucentSingleTop = 2
def countTranslucentSingleTask = 2
def countTranslucentSingleInstance = 3
/** 宿主中聲明的 TaskAffinity 的組數(shù) */
def countTask = 2
/**
* 是否使用 AppCompat 庫(kù)
* com.android.support:appcompat-v7:25.2.0
*/
def useAppCompat = false
/** HOST 向下兼容的插件版本 */
def compatibleVersion = 10
/** HOST 插件版本 */
def currentVersion = 12
/** plugins-builtin.json 文件名自定義,默認(rèn)是 "plugins-builtin.json" */
def builtInJsonFileName = "plugins-builtin.json"
/** 是否自動(dòng)管理 plugins-builtin.json 文件,默認(rèn)自動(dòng)管理 */
def autoManageBuiltInJsonFile = true
/** assert目錄下放置插件文件的目錄自定義,默認(rèn)是 assert 的 "plugins" */
def pluginDir = "plugins"
/** 插件文件的后綴自定義,默認(rèn)是".jar" 暫時(shí)支持 jar 格式*/
def pluginFilePostfix = ".jar"
/** 當(dāng)發(fā)現(xiàn)插件目錄下面有不合法的插件 jar (有可能是特殊定制 jar)時(shí)是否停止構(gòu)建,默認(rèn)是 true */
def enablePluginFileIllegalStopBuild = true
}
判斷project中是否含有AppPlugin類型插件癣亚,即我們?cè)谒拗黜?xiàng)目中是應(yīng)用了該類型插件,也就是是否在build.gradle中寫了:apply plugin: 'com.android.application'.所以获印,我們?cè)谑褂?code>apply plugin: 'replugin-host-gradle'時(shí)述雾,必須在
apply plugin: 'com.android.application'
之后。
如果希望判斷是否有l(wèi)ibraryPlugin,可以這樣寫:if (project.plugins.hasPlugin(LibraryPlugin))
獲取project中的AppExtension類型extension兼丰,即com.android.application projects的android extension.也就是在你的app模塊的build.gradle中定義的閉包:
android { ...}
遍歷android extension的Application variants 列表玻孟。這可以說(shuō)是 Hook Android gradle 插件的一種方式,因?yàn)橥ㄟ^(guò)遍歷applicationVariants鳍征,你可以修改屬性黍翎,名字,描述艳丛,輸出文件名等玩敏,如果是Android library庫(kù)斗忌,那么就將applicationVariants替換為libraryVariants。我們?cè)赼pp的build.gradle中這樣定義過(guò)閉包:
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)
}
}
}
}
其實(shí)這也是一種插件的創(chuàng)建方式旺聚,Hook Android gradle 插件動(dòng)態(tài)修改variants屬性值织阳,修改打包輸出的apk文件名。
創(chuàng)建自定義gradle插件砰粹,Gradle提供了多種方式(Gradle學(xué)習(xí)-----Gradle自定義插件提過(guò)):
1:在build.gradle腳本中直接創(chuàng)建(上述代碼即是)
2:在獨(dú)立Module中創(chuàng)建(replugin-host-gradle即是)
- 調(diào)用
addShowPluginTask(variant)
方法唧躲,代碼如下
// 添加 【查看所有插件信息】 任務(wù)
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
}
}
但是方法內(nèi)指定的task并未掛到android gradle task上,即task不會(huì)執(zhí)行碱璃。這個(gè)task是方便調(diào)試時(shí)查看插件信息的.
- 接下來(lái)調(diào)用
checkUserConfig
弄痹,源碼如下:
/**
* 檢查用戶配置項(xiàng)
*/
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 '--------------------------------------------------------------------------'
}
讀取一開始加載進(jìn)來(lái)的repluginHostConfig
配置信息做數(shù)據(jù)類型正確性校驗(yàn)。
- 關(guān)鍵代碼嵌器,下面一行代碼肛真,搞定了宿主中AndroidManifest.xml中的組件坑位生成,注意爽航,結(jié)合結(jié)構(gòu)概覽中的gradle Flow 看蚓让,這里只是生成組件坑位的xml代碼,最終的xml文件是在后續(xù)的task中拼裝出來(lái)的讥珍,稍后會(huì)講到历极。
def newManifest = ComponentsGenerator.generateComponent(appID, config)
該方法源碼如下:
def static generateComponent(def applicationID, def config) {
// 是否使用 AppCompat 庫(kù)(涉及到默認(rèn)主題)
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 進(jìn)程 */
xml.application {
/* 透明坑 */
config.countTranslucentStandard.times {
activity(
"${name}": "${applicationID}.${infix}N1NRTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}")
...
}
...
/* 不透明坑 */
config.countNotTranslucentStandard.times{
}
...
}
// 刪除 application 標(biāo)簽
def normalStr = writer.toString().replace("<application>", "").replace("</application>", "")
// println "${TAG} normalStr: ${normalStr}"
// 將單進(jìn)程和多進(jìn)程的組件相加
normalStr + generateMultiProcessComponent(applicationID, config)
}
主要作用就是基于 Groovy 的 MarkupBuilder api,根據(jù) RepluginConfig 類中的配置衷佃,拼出組件坑位的xml 字符串趟卸。就像搭積木一樣,以其中一組作為研究氏义。
config.countTranslucentStandard.times {
activity(
"${name}": "${applicationID}.${infix}N1NRTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}")
}
NOTE:config.countTranslucentStandard.times
含義:根據(jù)config.countTranslucentStandard
的值循環(huán)
生成的坑位:
<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
}
...
}
}
}
繼續(xù)回到 apply 方法锄列,接下來(lái)該到生成 RePluginHostConfig 的時(shí)候了,即 注釋中的host generate task
- 首先生成了 HostConfig 的gradle task 名字惯悠,并調(diào)用project的task()方法創(chuàng)建此Task邻邮。
- 指定了 generateHostConfigTask 的task任務(wù):自動(dòng)創(chuàng)建RePluginHostConfig.java至BuildConfig目錄。
generateHostConfigTask.doLast {
FileCreators.createHostConfig(project, variant, config)
}
注:createHostConfig(...)
也是根據(jù)配置類 RepluginConfig
中的配置信息拼裝生成的java文件吮螺。
//depends on build config task
if (generateBuildConfigTask) {
generateHostConfigTask.dependsOn generateBuildConfigTask
generateBuildConfigTask.finalizedBy generateHostConfigTask
}
因?yàn)榇藅ask中創(chuàng)建的RePluginHostConfig.java希望放置到編譯輸出目錄..\replugin-sample\host\app\build\generated\source\buildConfig\{productFlavors}\{buildTypes}\...
下饶囚,所以此task依賴于生成 BuildConfig.java 的task并設(shè)置為 BuildConfigTask 執(zhí)行完后,就執(zhí)行HostConfigTask鸠补。
關(guān)于gradle 的 task 相關(guān)知識(shí)萝风,可以去gradle 官網(wǎng)或某搜索引擎查看學(xué)習(xí),屬于字典型知識(shí)點(diǎn)紫岩,需要時(shí)候查閱下规惰。
生成 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
}
...
}
}
}
繼續(xù)回到 apply 方法,接下來(lái)該到生成 plugins-builtin.json 這個(gè)包含了插件信息的文件的時(shí)候了泉蝌,即 注釋中的json generate task
歇万。
- 首先生成個(gè)gradle task 名字揩晴,并調(diào)用project的task()方法創(chuàng)建此Task。
- 指定了 generateBuiltinJsonTask 的task任務(wù):掃描宿主\assets\plugins目錄下的插件文件贪磺,并基于apk文件規(guī)則解析出插件信息硫兰,包名,版本號(hào)等寒锚,然后拼裝成json文件劫映。
generateBuiltinJsonTask.doLast {
FileCreators.createBuiltinJson(project, variant, config)
}
設(shè)置 generateBuiltinJsonTask 的執(zhí)行依賴
//depends on build config task
if (mergeAssetsTask) {
generateBuiltinJsonTask.dependsOn mergeAssetsTask
mergeAssetsTask.finalizedBy generateBuiltinJsonTask
}
因?yàn)榇藅ask中創(chuàng)建的 plugins-builtin.json 希望放置到編譯輸出目錄...\replugin-sample\host\app\build\intermediates\assets\{productFlavors}\{buildTypes}\...
下,所以此task依賴于merge assets文件 的task并設(shè)置為 mergeAssetsTask
執(zhí)行完后刹前,就執(zhí)行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> 標(biāo)簽內(nèi)的配置信息合二為一。
至此喇喉,replugin-host-gradle
插件的工作就全部結(jié)束了祖今。