代码:
class NavigationTransform(private val target: Project) : Transform() {
private val navigationDataList = mutableListOf()
companion object {
const val NAV_RUNTIME_DESTINATION =
"Lcom/ukm/stroke/guard/ai/navigation/plugin/runtime/NavigationDestination;"
const val NAV_RUNTIME_NAV_TYPE =
"Lcom/ukm/stroke/guard/ai/navigation/plugin/runtime/NavigationDestination\$NavigationType"
private const val KEY_ROUTE = "route"
private const val KEY_TYPE = "type"
private const val KEY_STARTER = "asStarter"
private const val NAV_RUNTIME_PKG_NAME: String = "com.ukm.stroke.guard.ai.navigation.plugin.runtime"
private const val NAV_RUNTIME_REGISTRY_CLASS_NAME: String = "NavigationRegistry"
private const val NAV_RUNTIME_NAV_DATA_CLASS_NAME: String = "NavigationData"
private const val NAV_RUNTIME_NAV_LIST: String = "navigationList"
private const val NAV_RUNTIME_MODULE_NAME: String = "navigation-plugin-runtime"
}
override fun getName(): String {
println(" ||--NavigationTransform get name...")
return "NavigationTransform"
}
override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
println(" ||--NavigationTransform get input types...")
return TransformManager.CONTENT_CLASS
}
override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
println(" ||--NavigationTransform get scopes...")
return TransformManager.SCOPE_FULL_PROJECT
}
override fun isIncremental(): Boolean {
println(" ||--NavigationTransform is incremental...")
return false
}
override fun transform(transformInvocation: TransformInvocation?) {
println(" ||--NavigationTransform transform...")
super.transform(transformInvocation)
val inputs = transformInvocation?.inputs ?: return
val outputProvider = transformInvocation.outputProvider
outputProvider.deleteAll()
inputs.forEach {
// 1. 对 inputs -> directory -> class 文件进行遍历
// 2 .对 inputs -> jar -> class 文件进行遍历
it.directoryInputs.forEach { it ->
handleDirectoryClasses(it.file)
val outputDir = outputProvider.getContentLocation(
it.name,
it.contentTypes,
it.scopes,
Format.DIRECTORY
)
if (it.file.isFile) {
FileUtils.copyFile(it.file, outputDir)
} else {
FileUtils.copyDirectory(it.file, outputDir)
}
}
generateNavigationRegistry()
}
}
private fun generateNavigationRegistry() {
println(" ||--NavigationTransform generate navigation registry...")
// 利用 kotlinPoet 生成 NavigationRegistry.kt 文件,存放在 nav-plugin-runtime 模块下;
// 用于记录项目中所有的路由节点数据
//1. 生成成员变量 val navList:ArrayList<NavData>
val navData = ClassName(NAV_RUNTIME_PKG_NAME, NAV_RUNTIME_NAV_DATA_CLASS_NAME)
val arrayList = ClassName("kotlin.collections", "ArrayList")
//2.生成 get 方法返回值类型 List<NavData>
val list = ClassName("kotlin.collections", "List")
val arrayListOfNavData = arrayList.parameterizedBy(navData)
val listOfNavData = list.parameterizedBy(navData)
//3. 生成 object Class init{ 代码块 }
val statements = java.lang.StringBuilder()
navigationDataList.forEach {
statements.append(
String.format(
"navList.add(NavigationData(\"%s\",\"%s\",%s))",
it.route,
it.className,
it.type
)
)
statements.append("\n")
}
// 4. 向 object class 添加成员属性 navList 并且进行初始化赋值
val property =
PropertySpec.builder(NAV_RUNTIME_NAV_LIST, arrayListOfNavData, KModifier.PRIVATE)
.initializer(CodeBlock.builder().addStatement("ArrayList<NavData>()").build())
.build()
// 5.构建 get 方法 并且生成代码块
val function = FunSpec.builder("get").returns(listOfNavData).addCode(
CodeBlock.builder()
.addStatement("val list = ArrayList<NavData>()\n list.addAll(navList)\n return list\n")
.build()
).build()
// 6. 构建 object NavRegistry class. 并且填充属性、int{} get 方法
val typeSpec = TypeSpec.objectBuilder(NAV_RUNTIME_REGISTRY_CLASS_NAME)
.addProperty(property)
.addInitializerBlock(CodeBlock.builder().addStatement(statements.toString()).build())
.addFunction(function)
.build()
// 7. 生成文件、添加注释和导包
val fileSpec = FileSpec.builder(NAV_RUNTIME_PKG_NAME, NAV_RUNTIME_REGISTRY_CLASS_NAME)
.addComment("this file is generated by auto,please do not modify!!!")
.addType(typeSpec)
.addImport(NavigationDestination.NavigationType::class.java, "Fragment", "Dialog", "Activity", "None")
.build()
// 8. 写入文件
val runtimeProject = target.rootProject.findProject(NAV_RUNTIME_MODULE_NAME)
assert(runtimeProject == null) {
throw GradleException("cant found $NAV_RUNTIME_MODULE_NAME")
}
val sourceSet = runtimeProject!!.extensions.findByName("sourceSets") as SourceSetContainer
val outputFileDir = sourceSet.first().java.srcDirs.first().absoluteFile
println("NavTransform outputFileDir:${outputFileDir.absolutePath}")
fileSpec.writeTo(outputFileDir)
}
private fun handleDirectoryClasses(file: File) {
println(" ||--NavigationTransform handle directory classes...")
if (file.isDirectory) {
file.listFiles()?.forEach {
handleDirectoryClasses(it)
}
} else if (file.extension.endsWith("class")) {
val inputStream = FileInputStream(file)
println("NavigationTransform handleDirectoryClasses-zipEntry:${file.name}")
visitClass(inputStream)
inputStream.close()
}
}
private fun handleJarClasses(file: File) {
println(" ||--NavigationTransform handle jar classes...")
var zipFile = ZipFile(file)
zipFile.stream().forEach {
if (it.name.endsWith("class")) {
println("NavTransform handleJarClasses-zipEntry:${it.name}")
val inputStream = zipFile.getInputStream(it)
visitClass(inputStream)
inputStream.close()
}
}
zipFile.close()
}
private fun visitClass(inputStream: InputStream) {
var classReader = ClassReader(inputStream)
var classVisitor = object : ClassVisitor(Opcodes.ASM9) {
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
// return super.visitAnnotation(descriptor, visible)
if (descriptor != NAV_RUNTIME_DESTINATION) {
return object : AnnotationVisitor(Opcodes.ASM9) {}
}
val annotationVisitor = object : AnnotationNode(Opcodes.ASM9, "") {
var route = ""
var asStarter = false
var type = NavigationDestination.NavigationType.None
override fun visit(name: String?, value: Any?) {
super.visit(name, value)
if (name == KEY_ROUTE) {
route = value as String
} else if (name == KEY_STARTER) {
asStarter = value as Boolean
}
}
override fun visitEnum(name: String?, descriptor: String?, value: String?) {
super.visitEnum(name, descriptor, value)
if (name == KEY_TYPE) {
assert(value == null) {
throw GradleException("NavigationDestination\$type must be one of Fragment,Activity,Dialog")
}
type = NavigationDestination.NavigationType.valueOf(value!!)
}
}
override fun visitEnd() {
super.visitEnd()
val navData = NavigationData(route, classReader.className.replace("/", "."), type)
navigationDataList.add(navData)
}
}
return annotationVisitor
}
}
classReader.accept(classVisitor, EXPAND_FRAMES)
}
}
构建日志:
Configure project :app
||–NavigationPlugin apply…
||–NavigationPlugin Register transform ‘NavigationTransform’…
Task :navigation-plugin:generatePomFileForNavigationPluginPluginMarkerMavenPublication
Task :navigation-plugin:publishNavigationPluginPluginMarkerMavenPublicationToMavenLocal
Task :navigation-plugin:pluginDescriptors UP-TO-DATE
Task :navigation-plugin:processResources UP-TO-DATE
Task :navigation-plugin:sourcesJar UP-TO-DATE
Task :navigation-plugin:generatePomFileForPluginMavenPublication
Task :navigation-plugin:compileKotlin
w: file:///F:/Projects/Master/StrokeGuard-AI-App/navigation-plugin/src/main/java/com/ukm/stroke/guard/ai/navigation/plugin/NavigationPlugin.kt:20:27 ‘registerTransform(Transform, vararg Any): Unit’ is deprecated. The transform API is planned to be removed in Android Gradle plugin 8.0.
w: file:///F:/Projects/Master/StrokeGuard-AI-App/navigation-plugin/src/main/java/com/ukm/stroke/guard/ai/navigation/plugin/NavigationTransform.kt:78:42 Name shadowed: it
Task :navigation-plugin:compileJava NO-SOURCE
Task :navigation-plugin:classes UP-TO-DATE
Task :navigation-plugin:jar UP-TO-DATE
Task :navigation-plugin:javadoc NO-SOURCE
Task :navigation-plugin:javadocJar UP-TO-DATE
Task :navigation-plugin:generateMetadataFileForPluginMavenPublication
Task :navigation-plugin:publishPluginMavenPublicationToMavenLocal
Task :navigation-plugin:publishToMavenLocal
BUILD SUCCESSFUL in 6s
11 actionable tasks: 6 executed, 5 up-to-date
Build Analyzer results available