引言
上一阶段,我们学习了sample-manager模块的设计理念和具体实现:
(1)【插件&热修系列】Shadow源码解析之sample-manager(一)
(2)【插件&热修系列】Shadow源码解析之sample-manager(二)
到这里,我们的热修已经完成了《宿主》 和 《管理插件》模块
那么下一个阶段就是插件模块了,比如:
- 多个业务插件生产流程中是怎么打成zip发布的?
- 每个插件是怎么写的?
- 插件里面的组件是怎么支持的?
- .........
下面我们将以《插件生产流程中是怎么打成zip发布的》内容,开启新的征程
概要
这个是shadow插件zip包里面的内容,有4个插件,1个配置文件
配置文件如上,可以看出是插件的一些版本信息等
本章将学习如何用gradle插件生成这些信息
实现流程
1.实现目的
本次主要实现把《前面讲到的 sample-manager.apk》 进行打包成zip
2.效果展示
执行打包任务 然后会生产zip包到指定目录
3.插件定义/上传/使用相关gradle配置
(1)插件定义
这里使用的定义方式是新版本的方式,和以前讲的不太一样
新方式叫Composing builds,以前的方式叫 buildSrc
主要区别是:
- buildSrc,A change in buildSrc causes the whole project to become out-of-date(意思就是只要有变化了,那么整个工程都会编译,效率不高),具体细节戳这里>>>
- Composing builds,A composite build is simply a build that includes other builds. In many ways a composite build is similar to a Gradle multi-project build, except that instead of including single projects, complete builds are included.(意思是复合构建只是包含其他构建的构建. 在许多方面,复合构建类似于 Gradle 多项目构建,不同之处在于,它包括完整的 builds ,而不是包含单个 projects,特点是其编译速度快),具体戳这里>>>
除了看官方文档外,网上也有比较多不错的介绍,如字节同学的分享
(2)插件上传 这里用了比较简单的上传方式,上传到本地工程的repo下
(3)插件引入
(4)插件使用
我们使用配置的是把sample-manager输出的apk打包成zip
4.工程简介
这个是把官方的代码合并之后的效果,原官方结构如下:
5.源码解析
(1)寻找本地androidJar,然后自定义 classPool,完成disable_shadow_transform 开关功能
因为开关功能不是本章重点,所以实现暂时注释,了解下其代码大概作用即可
(2)自定义拓展
ShadowExtension,是一个集合的实现
open class ShadowExtension {
var transformConfig = TransformConfig()
fun transform(action: Action<in TransformConfig>) {
action.execute(transformConfig)
}
}
class TransformConfig {
var useHostContext: Array<String> = emptyArray()
}
PackagePluginExtension,具体样子如下:
open class PackagePluginExtension {
var loaderApkProjectPath = ""
var runtimeApkProjectPath = ""
var archivePrefix = ""
var archiveSuffix = ""
var destinationDir = ""
var uuid = ""
var version: Int = 0
var uuidNickName = ""
var compactVersion: Array<Int> = emptyArray()
var buildTypes: NamedDomainObjectContainer<PluginBuildType>
}
里面的 PluginBuildType 如下:
open class PluginBuildType {
var name = ""
var loaderApkConfig: Tuple2<String, String> = Tuple2("", "")
var runtimeApkConfig: Tuple2<String, String> = Tuple2("", "")
lateinit var pluginApks: NamedDomainObjectContainer<PluginApkConfig>
}
里面的 PluginApkConfig 如下:
open class PluginApkConfig {
var name = ""
var partKey = ""
var businessName = ""
var apkName = ""
var apkPath = ""
var buildTask = ""
var dependsOn: Array<String> = emptyArray()
var hostWhiteList: Array<String> = emptyArray()
}
(3)task创建
//根据配置的 pluginTypes 创建执行任务
val tasks = mutableListOf<Task>()
for (i in buildTypes) {
//组织apk和json等插件为zip
val task = createPackagePluginTask(project, i)
tasks.add(task)
}
根据 buildTypes 来构建任务(createPackagePluginTask)
buildTypes是上面的PluginBuildType,本例子只有一个debug类型
然后我们来看下 createPackagePluginTask 的具体实现:
internal fun createPackagePluginTask(project: Project, buildType: PluginBuildType): Task {
/**
* 目录
* */
val targetConfigFile = File(project.projectDir.absolutePath + "/generatePluginConfig/${buildType.name}/config.json")
/**
* 1)依赖于 createGenerateConfigTask
* 2)压缩zip包
* */
return project.tasks.create("package${buildType.name.capitalize()}Plugin", Zip::class.java) {
System.err.println("PackagePluginTask task start run...")
//runtime apk file
val runtimeApkName: String = buildType.runtimeApkConfig.first
var runtimeFile: File? = null
if (runtimeApkName.isNotEmpty()) {
System.err.println("runtime apk...")
runtimeFile = ShadowPluginHelper.getRuntimeApkFile(project, buildType, false)
}
//loader apk file
val loaderApkName: String = buildType.loaderApkConfig.first
var loaderFile: File? = null
if (loaderApkName.isNotEmpty()) {
System.err.println("loader apk...")
loaderFile = ShadowPluginHelper.getLoaderApkFile(project, buildType, false)
}
//config file
//不存在:/Users/yabber/AndroidStudioProjects/DemoHot/sample-plugin-app/build + /intermediates
//val targetConfigFile = File(project.buildDir.absolutePath + "/intermediates/generatePluginConfig/${buildType.name}/config.json")
//val targetConfigFile = File(project.projectDir.absolutePath + "/generatePluginConfig/${buildType.name}/config.json")
val result = targetConfigFile.parentFile.mkdirs()
System.err.println("mkdirs config.json parentFile dir, result = " + result)
System.err.println("targetConfigFile = " + targetConfigFile.absoluteFile)
//all plugin apks
val pluginFiles: MutableList<File> = mutableListOf()
for (i in buildType.pluginApks) {
val file = ShadowPluginHelper.getPluginFile(project, i, false)
System.err.println("pluginFile = " + file.absoluteFile)
pluginFiles.add(file)
}
it.group = "plugin"
it.description = "打包插件"
it.outputs.upToDateWhen { false }
if (runtimeFile != null) {
pluginFiles.add(runtimeFile)
}
if (loaderFile != null) {
pluginFiles.add(loaderFile)
}
it.from(pluginFiles, targetConfigFile)// from
val packagePlugin = project.extensions.findByName("packagePlugin")
val extension = packagePlugin as PackagePluginExtension
val suffix = if (extension.archiveSuffix.isEmpty()) "" else extension.archiveSuffix
val prefix = if (extension.archivePrefix.isEmpty()) "plugin" else extension.archivePrefix
if (suffix.isEmpty()) {
it.archiveName = "$prefix-${buildType.name}.zip"
} else {
it.archiveName = "$prefix-${buildType.name}-$suffix.zip"
}
//destinationDir
it.destinationDir = File(if (extension.destinationDir.isEmpty()) "${project.rootDir}/build" else extension.destinationDir)
System.err.println("destinationDir = " + it.destinationDir.absoluteFile)
System.err.println("PackagePluginTask task end...")
}.dependsOn(createGenerateConfigTask(project, buildType))
}
这里做了如下几件事:
(1)创建任务 package${buildType.name.capitalize()}Plugin
(2)准备插件apk File,如:runtime apk / loader apk / sample-manager apk / 业务插件 apk
(3)准备好一系列参数,然后把插件拷贝到指定目录
(4)创建另外一个任务,并且让上面任务依赖这个任务
下面我们来看下另外一个任务(createGenerateConfigTask)做了啥?
private fun createGenerateConfigTask(project: Project, buildType: PluginBuildType): Task {
System.err.println("GenerateConfigTask task run ... ")
/**
* 目录
* */
val targetConfigFile = File(project.projectDir.absolutePath + "/generatePluginConfig/${buildType.name}/config.json")
val packagePlugin = project.extensions.findByName("packagePlugin")
val extension = packagePlugin as PackagePluginExtension
//runtime apk build task
val runtimeApkName = buildType.runtimeApkConfig.first
var runtimeTask = ""
if (runtimeApkName.isNotEmpty()) {
runtimeTask = buildType.runtimeApkConfig.second
System.err.println("runtime task = $runtimeTask")
//println("runtime task = $runtimeTask")
}
//loader apk build task
val loaderApkName = buildType.loaderApkConfig.first
var loaderTask = ""
if (loaderApkName.isNotEmpty()) {
loaderTask = buildType.loaderApkConfig.second
System.err.println("loader task = $loaderTask")
//println("loader task = $loaderTask")
}
//插件工程任务,如::sample-manager:assembleDebug
val pluginApkTasks: MutableList<String> = mutableListOf()
for (i in buildType.pluginApks) {
val task = i.buildTask
System.err.println("pluginApkProjects task = $task")
//println("pluginApkProjects task = $task")
pluginApkTasks.add(task)
}
/**
* 1)依赖于 pluginApkTasks
* */
val task = project.tasks.create("generate${buildType.name.capitalize()}Config") {
it.group = "plugin"
it.description = "生成插件配置文件"
it.outputs.file(targetConfigFile)
it.outputs.upToDateWhen { false }
}
.dependsOn(pluginApkTasks)//依赖于插件工程任务,如::sample-manager:assembleDebug
.doLast {
System.err.println("generate json Config task begin")
//println("generateConfig task begin")
val json = extension.toJson(project, loaderApkName, runtimeApkName, buildType)
System.err.println("json = " + json)
val bizWriter = BufferedWriter(FileWriter(targetConfigFile))
bizWriter.write(json.toJSONString())
bizWriter.newLine()
bizWriter.flush()
bizWriter.close()
System.err.println("generateConfig task done")
}
if (loaderTask.isNotEmpty()) {
task.dependsOn(loaderTask)
}
if (runtimeTask.isNotEmpty()) {
task.dependsOn(runtimeTask)
}
return task
}
主要做了几件事:
(1)参数准备,如:拓展里面读取参数
(2)创建任务,执行json文件生成,并且任务在指定的任务后面执行(如:在sample-manager:assembleDebug后)
到这里,可以看到任务时序大致是这样的:
- 先是插件生成apk,如:sample-manager:assembleDebug后
- 然后是根据生成的apk,生成json文件
- 最后是把apk和json打包成zip
源码地址
结尾
哈哈,该篇就写到这里(一起体系化学习,一起成长)
Tips
深挖安卓移动领域,沉淀安卓应用等技术,微信“ DaviZgx ”,欢迎添加进群交流