Replugin
是360开源的一个插件化框架,源码地址:https://github.com/Qihoo360/RePlugin,属于一种占坑类的插件化方案,整个框架分为四部分:宿主编译插件、插件工程编译插件、宿主依赖库、插件依赖库。今天分享的是宿主编译插件源码,该插件的作用是在编译时将四大组件的坑位预置进宿主程序中,并生成内置插件的配置信息。
很显然标准的
gradle
插件工程,编码语言为groovy
,编译工具为gradle
,本文不讨论gradle
插件工程相关知识,本文只分析该插件在宿主编译时做了哪些工作。
从replugin-host-gradle.properties
文件可以看到整个plugin
的入口在类com.qihoo360.replugin.gradle.host.Replugin
中,首先就从这个文件开始入手吧。
源码路径:com.qihoo360.replugin.gradle.host.Replugin.groovy
/**整个插件的调用入口方法*/
@Override
public void apply(Project project) {
this.project = project
/* Extensions */
//创建extensions,然后再bulid.gradle文件里就可以,修改RepluginConfig里的默认配置了。
project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig)
/*app工程才会执行,Library项目直接过滤了*/
if (project.plugins.hasPlugin(AppPlugin)) {
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
addShowPluginTask(variant)
//获取用户bulid.gradle文件里的配置
if (config == null) {
config = project.extensions.getByName(AppConstant.USER_CONFIG)
checkUserConfig(config)
}
def generateBuildConfigTask = VariantCompat.getGenerateBuildConfigTask(variant)
def appID = generateBuildConfigTask.appPackageName
def newManifest = ComponentsGenerator.generateComponent(appID, config)
println "${TAG} countTask=${config.countTask}"
def variantData = variant.variantData
def scope = variantData.scope
//host generate task
def generateHostConfigTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "HostConfig")
def generateHostConfigTask = project.task(generateHostConfigTaskName)
//生成RePluginHostConfig.java
generateHostConfigTask.doLast {
FileCreators.createHostConfig(project, variant, config)
}
generateHostConfigTask.group = AppConstant.TASKS_GROUP
//depends on build config task
if (generateBuildConfigTask) {
generateHostConfigTask.dependsOn generateBuildConfigTask
generateBuildConfigTask.finalizedBy generateHostConfigTask
}
//json generate task
def generateBuiltinJsonTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "BuiltinJson")
def generateBuiltinJsonTask = project.task(generateBuiltinJsonTaskName)
//plugins-builtin.json
generateBuiltinJsonTask.doLast {
FileCreators.createBuiltinJson(project, variant, config)
}
generateBuiltinJsonTask.group = AppConstant.TASKS_GROUP
//depends on mergeAssets Task
def mergeAssetsTask = VariantCompat.getMergeAssetsTask(variant)
if (mergeAssetsTask) {
generateBuiltinJsonTask.dependsOn mergeAssetsTask
mergeAssetsTask.finalizedBy generateBuiltinJsonTask
}
variant.outputs.each { output ->
VariantCompat.getProcessManifestTask(output).doLast {
println "${AppConstant.TAG} processManifest: ${it.outputs.files}"
it.outputs.files.each { File file ->
updateManifest(file, newManifest)
}
}
}
}
}
}
上面代码出现多次使用了一个工具类VariantCompat
,该类是做gradle
版本兼容的,主要目的是从Android Gradle Task
里获取对应的task
,例如方法VariantCompat.getGenerateBuildConfigTask(variant)
就是获取generateBuildConfigTask
的,可以在Android Studio中打开gradle任务视图,根据不同的variant
即可获取到不同的任务。
/**
* 动态生成插件化框架中需要的组件
*
* @param applicationID 宿主的 applicationID
* @param config 用户配置
* @return String 插件化框架中需要的组件
*/
def newManifest = ComponentsGenerator.generateComponent(appID, config)
//生成结果
//
// ...
//
//...
//
//...
该方法通过gradle
文件中的配置坑位个数来生成四大组件的预置坑位,生成代码很简单,就是for循环生成xml文件。有兴趣可以点进去看,源码路径:com.qihoo360.replugin.gradle.host.handlemanifest.ComponentsGenerator.groovy
源码位置:com.qihoo360.replugin.gradle.host.creator.impl.json.PluginBuiltinJsonCreator#getFileContent;
//查找插件文件并抽取信息,如果没有就直接返回null
File pluginDirFile = new File(fileDir?.getAbsolutePath() + File.separator + config.pluginDir)
//遍历assets/plugins下的所有插件。
new File(fileDir.getAbsolutePath() + File.separator + config.pluginDir)
.traverse(type: FileType.FILES, nameFilter: ~/.*\${config.pluginFilePostfix}/) {
PluginInfoParser parser = null
try {
//利用开源库解析插件APK的版本号等Meta信息。
parser = new PluginInfoParser(it.absoluteFile, config)
} catch (Exception e) {
if (config.enablePluginFileIllegalStopBuild) {
throw new Exception(e)
}
}
if (null != parser) {
pluginInfos << parser.pluginInfo
}
}
//插件为0个
if (pluginInfos.isEmpty()) {
return null
}
//构建插件们的json信息
def jsonOutput = new JsonOutput()
String pluginInfosJson = jsonOutput.toJson(pluginInfos)
//格式化打印插件们的json信息
return pluginInfosJson
从代码中看到,此处直接从assets里扫描并生成内置插件的json
信息。这里解析插件apk
文件用到了一个开源库net.dongliu:apk-parser:2.2.0
,https://github.com/hsiafan/apk-parser,有兴趣可以关注一下。
直接上代码吧,没有任何难度,就是简单的替换,找到所有的AndroidManifest.xml
把生产的坑位append
上去。
def updateManifest(def file, def newManifest) {
// 除了目录和AndroidManifest.xml之外,还可能会包含manifest-merger-debug-report.txt等不相干的文件,过滤它
if (file == null || !file.exists() || newManifest == null) return
if (file.isDirectory()) {
println "${AppConstant.TAG} updateManifest: ${file}"
file.listFiles().each {
updateManifest(it, newManifest)
}
} else if (file.name.equalsIgnoreCase("AndroidManifest.xml")) {
appendManifest(file, newManifest)
}
}
def appendManifest(def file, def content) {
if (file == null || !file.exists()) return
println "${AppConstant.TAG} appendManifest: ${file}"
def updatedContent = file.getText("UTF-8").replaceAll("", content + "")
file.write(updatedContent, 'UTF-8')
}
看完源码后,Replugin
宿主的gradle
插件没有干什么高大上的活,都是一些配置生成的体力活。理论上来讲对编译性能影响也不大,耗时多得地方可能就在扫描内置插件的任务上了。但是Replugin
生成了非常多得坑位,接入之后,最终的AndroidManifest.xml
会多出200多个坑位,但这两百多个坑位是没有对应的类文件的,后续阅读宿主程序源码后再行分析。整体来看Replugin
在编译器对宿主的入侵是很小的。