replugin-host-gradle,针对宿主应用执行的构建任务:生成带 RePlugin 插件坑位的 AndroidManifest.xml(允许自定义数量);生成 RepluginHostConfig 类,方便插件框架读取并自定义其属性;生成 plugins-builtin.json,json中含有插件应用的信息,包名,插件名,插件路径等。
1、生成有坑位的组件的AndroidManifest.xml
gradle 插件入口 Replugin.groovy
@Override
public void apply(Project project) {
println "${TAG} Welcome to replugin world ! "
this.project = project
/* Extensions */
project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig)
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 generateBuildConfigTask = VariantCompat.getGenerateBuildConfigTask(variant)
def appID = generateBuildConfigTask.appPackageName
def newManifest = ComponentsGenerator.generateComponent(appID, config) //宿主中AndroidManifest.xml中的组件坑位生成
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)
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)
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)
}
}
}
}
}
}
def newManifest = ComponentsGenerator.generateComponent(appID, config)
def static generateComponent(def applicationID, def config) {
// 是否使用 AppCompat 库(涉及到默认主题)
if (config.useAppCompat) {
themeNTS = THEME_NTS_USE_APP_COMPAT
} else {
themeNTS = THEME_NTS_NOT_USE_APP_COMPAT
}
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
/* UI 进程 */
xml.application {
/* 需要编译期动态修改进程名的组件*/
String pluginMgrProcessName = config.persistentEnable ? config.persistentName : applicationID
// 常驻进程Provider
provider(
"${name}":"com.qihoo360.replugin.component.process.ProcessPitProviderPersist",
"${authorities}":"${applicationID}.loader.p.main",
"${exp}":"false",
"${process}":"${pluginMgrProcessName}")
provider(
"${name}":"com.qihoo360.replugin.component.provider.PluginPitProviderPersist",
"${authorities}":"${applicationID}.Plugin.NP.PSP",
"${exp}":"false",
"${process}":"${pluginMgrProcessName}")
// ServiceManager 服务框架
provider(
"${name}":"com.qihoo360.mobilesafe.svcmanager.ServiceProvider",
"${authorities}":"${applicationID}.svcmanager",
"${exp}":"false",
"${multiprocess}":"false",
"${process}":"${pluginMgrProcessName}")
service(
"${name}":"com.qihoo360.replugin.component.service.server.PluginPitServiceGuard",
"${process}":"${pluginMgrProcessName}")
/* 透明坑 */
config.countTranslucentStandard.times {
activity(
"${name}": "${applicationID}.${infix}N1NRTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}")
}
config.countTranslucentSingleTop.times {
activity(
"${name}": "${applicationID}.${infix}N1STPTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}",
"${launchMode}": "singleTop")
}
......
}
}
2、生成 RepluginHostConfig
Replugin.groovy apply(Project project)
//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
FileCreators.groovy
static def createHostConfig(project, variant, config) {
def creator = new RePluginHostConfigCreator(project, variant, config)
create(creator)
}
RePluginHostConfigCreator.groovy
@Override
String getFileContent() {
return """
package com.qihoo360.replugin.gen;
/**
* 注意:此文件由插件化框架自动生成,请不要手动修改。
*/
public class RePluginHostConfig {
// 常驻进程名字
public static String PERSISTENT_NAME = "${config.persistentName}";
// 是否使用“常驻进程”(见PERSISTENT_NAME)作为插件的管理进程。若为False,则会使用默认进程
public static boolean PERSISTENT_ENABLE = ${config.persistentEnable};
// 背景透明的坑的数量(每种 launchMode 不同)
public static int ACTIVITY_PIT_COUNT_TS_STANDARD = ${config.countTranslucentStandard};
public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TOP = ${config.countTranslucentSingleTop};
public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TASK = ${config.countTranslucentSingleTask};
public static int ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE = ${
config.countTranslucentSingleInstance
};
// 背景不透明的坑的数量(每种 launchMode 不同)
public static int ACTIVITY_PIT_COUNT_NTS_STANDARD = ${config.countNotTranslucentStandard};
public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP = ${config.countNotTranslucentSingleTop};
public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK = ${config.countNotTranslucentSingleTask};
public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE = ${
config.countNotTranslucentSingleInstance
};
// TaskAffinity 组数
public static int ACTIVITY_PIT_COUNT_TASK = ${config.countTask};
// 是否使用 AppCompat 库
public static boolean ACTIVITY_PIT_USE_APPCOMPAT = ${config.useAppCompat};
//------------------------------------------------------------
// 主程序支持的插件版本范围
//------------------------------------------------------------
// HOST 向下兼容的插件版本
public static int ADAPTER_COMPATIBLE_VERSION = ${config.compatibleVersion};
// HOST 插件版本
public static int ADAPTER_CURRENT_VERSION = ${config.currentVersion};
}"""
}
3、生成 plugins-builtin.json
Replugin.groovy apply(Project project)
//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
PluginBuiltinJsonCreator.groovy
@Override
String getFileContent() {
//查找插件文件并抽取信息,如果没有就直接返回null
File pluginDirFile = new File(fileDir?.getAbsolutePath() + File.separator + config.pluginDir)
if (!pluginDirFile.exists()) {
println "${AppConstant.TAG} The ${pluginDirFile.absolutePath} does not exist "
println "${AppConstant.TAG} pluginsInfo=null"
return null
}
new File(fileDir.getAbsolutePath() + File.separator + config.pluginDir)
.traverse(type: FileType.FILES, nameFilter: ~/.*\${config.pluginFilePostfix}/) {
PluginInfoParser parser = null
try {
parser = new PluginInfoParser(it.absoluteFile, config)
} catch (Exception e) {
if (config.enablePluginFileIllegalStopBuild) {
System.err.println "${AppConstant.TAG} the plugin(${it.absoluteFile.absolutePath}) is illegal !!!"
throw new Exception(e)
}
}
if (null != parser) {
pluginInfos << parser.pluginInfo
}
}
//插件为0个
if (pluginInfos.isEmpty()) {
println "${AppConstant.TAG} pluginsSize=0"
println "${AppConstant.TAG} pluginsInfo=null"
return null
}
//构建插件们的json信息
def jsonOutput = new JsonOutput()
String pluginInfosJson = jsonOutput.toJson(pluginInfos)
//格式化打印插件们的json信息
println "${AppConstant.TAG} pluginsSize=${pluginInfos.size()}"
println "${AppConstant.TAG} pluginsInfo=${jsonOutput.prettyPrint(pluginInfosJson)}"
return pluginInfosJson
}
从manifest的xml中抽取PluginInfo信息
PluginInfoParser.groovy
public PluginInfoParser(File pluginFile, def config) {
pluginInfo = new PluginInfo()
ApkFile apkFile = new ApkFile(pluginFile)
String manifestXmlStr = apkFile.getManifestXml()
ByteArrayInputStream inputStream = new ByteArrayInputStream(manifestXmlStr.getBytes("UTF-8"))
SAXParserFactory factory = SAXParserFactory.newInstance()
SAXParser parser = factory.newSAXParser()
parser.parse(inputStream, this)
String fullName = pluginFile.name
pluginInfo.path = config.pluginDir + "/" + fullName
String postfix = config.pluginFilePostfix
pluginInfo.name = fullName.substring(0, fullName.length() - postfix.length())
}
public PluginInfo getPluginInfo() {
return pluginInfo;
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if ("meta-data" == qName) {
switch (attributes.getValue(ANDROID_NAME)) {
case TAG_NAME:
pluginInfo.name = attributes.getValue(ANDROID_VALUE)
break;
case TAG_VERSION_LOW:
pluginInfo.low = new Long(attributes.getValue(ANDROID_VALUE))
break;
case TAG_VERSION_HIGH:
pluginInfo.high = new Long(attributes.getValue(ANDROID_VALUE))
break;
case TAG_VERSION_VER:
pluginInfo.ver = new Long(attributes.getValue(ANDROID_VALUE))
break
case TAG_FRAMEWORK_VER:
pluginInfo.frm = new Long(attributes.getValue(ANDROID_VALUE))
break
default:
break
}
} else if ("manifest" == qName) {
pluginInfo.pkg = attributes.getValue("package")
pluginInfo.ver = new Long(attributes.getValue("android:versionCode"))
}
}