Skip to content

Commit

Permalink
navigation-transform
Browse files Browse the repository at this point in the history
  • Loading branch information
foolishchow committed May 21, 2021
1 parent 8504aa8 commit 15a5653
Show file tree
Hide file tree
Showing 22 changed files with 1,077 additions and 117 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ backup/
.idea
/multi-module/build/
/view-binding/build/
/buildSrc/build/
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ android {
}

debug {
shrinkResources true
//shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ open class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
val controller = navController
NavigationManager.attach(controller, R.navigation.main)
println("")
//val navDestinations = MainActivity(controller)
//val inflate = controller!!.navInflater.inflate(R.navigation.main)
//val displayName = inflate.navigatorName
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/navigation/main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
<fragment
android:id="@+id/child"
android:name="me.foolishchow.autoparamdemo.fragment.MyFragment"
android:label="ChildFragment" />
android:label="ChildFragment1" />
</navigation>
4 changes: 0 additions & 4 deletions app/src/main/res/raw/discard.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,11 @@ package me.foolishchow.android.navigationprocessor

import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import me.foolishchow.android.navigationprocessor.extensions.AaptRules
import me.foolishchow.android.navigationprocessor.extensions.NavigationTaskName
import me.foolishchow.android.navigationprocessor.extensions.navigationDir
import org.gradle.api.DefaultTask
import me.foolishchow.android.navigationprocessor.extensions.*
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.closureOf
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import java.io.*

/**
* Description:
Expand All @@ -34,98 +23,64 @@ class NavigationPlugin : Plugin<Project> {
val android: BaseAppModuleExtension = project.extensions.getByName("android") as BaseAppModuleExtension

android.applicationVariants.configureEach {
postProcessProguardRules(this, project)
registerPostProcessProguardRules(this, project)
registerNavigationTask(this, project)
}
}


private fun registerNavigationTask(variant: ApplicationVariant, project: Project) {
val android: BaseAppModuleExtension = project.extensions
private fun registerNavigationTask(variant: ApplicationVariant, _project: Project) {
val android: BaseAppModuleExtension = _project.extensions
.getByName("android") as BaseAppModuleExtension

val sourceSet = variant.sourceSets.find { it.name == variant.name } ?: return

android.sourceSets.forEach { source ->
if (source.name == sourceSet.name) {
source.java.srcDir(project.navigationDir(variant))
source.java.srcDir(_project.navJavaDir(variant))
source.res.srcDir(_project.navResDir(variant))
}
}

val task = project.tasks.create(mapOf(
val task = _project.tasks.create(mapOf(
"name" to variant.NavigationTaskName,
"group" to "auto-param",
"type" to NavigationTask::class.java
), closureOf<NavigationTask> {
val files = mutableListOf<File>()
var lastModified = -1L
variant.sourceSets.forEach { sourceSet ->
sourceSet.resDirectories.forEach { res ->
project.fileTree(File(res, "navigation")).forEach { file ->
files.add(file)
lastModified = file.lastModified().coerceAtLeast(lastModified)
}
}
}
this.inputs.property("variant", variant.name)
this.inputs.files(files.toTypedArray())
this.outputs.dir("${project.buildDir}/generated/source/navigation/${variant.name}")
.withPropertyName("outputDir")
inputs.property("lastModified",lastModified)
inputs.files(files.toTypedArray())
resDir = project.file(project.navResDir(variant))
javaDir = project.file(project.navJavaDir(variant))
ruleFile = project.file(project.navRuleFile(variant))
})

val preBuild = "pre${variant.name.capitalize()}Build"
project.tasks.findByName(preBuild)?.dependsOn(task)
_project.tasks.findByName(preBuild)?.dependsOn(task)
}

private fun postProcessProguardRules(variant: ApplicationVariant, project: Project) {
private fun registerPostProcessProguardRules(variant: ApplicationVariant, _project: Project) {
variant.outputs.forEach { output ->
val taskName = "process${variant.name.capitalize()}Resources"
val task = project.tasks.findByName(taskName)
val task = _project.tasks.findByName(taskName)

task?.let {
output.processResourcesProvider.orNull?.doLast {
val rulesPath = project.AaptRules(variant)
val rules = Rules()

val stream = BufferedReader(FileReader(rulesPath))
var str: String?
var line = 0
while (stream.readLine().also { str = it } != null) {
rules.add(str, line)
line++
}
rules.add(str, line)
rules.rules.forEach { rule ->
println(rule.className)
}
editAaptRule(project, variant)
}
}
}
}
}

class Rules {
val rules = mutableListOf<Rule>()
var rule = Rule()

init {
rules.add(rule)
}

fun add(str: String?, line: Int) {
if (str.isNullOrBlank()) {
rule = Rule()
rules.add(rule)
} else if (str.startsWith("# Referenced at")) {
rule.references.add(str)
} else if (str.startsWith("-keep class ")) {
rule.className = str
rule.line = line
}
}
}

class Rule {
var line = -1
var className: String? = null
val references = mutableListOf<String>()

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,92 +7,103 @@ import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import groovy.util.Node
import groovy.util.XmlParser
import me.foolishchow.android.navigationprocessor.extensions.navigationDir
import me.foolishchow.android.navigationprocessor.extensions.resId
import me.foolishchow.android.navigationprocessor.extensions.resourceSymbol
import me.foolishchow.android.navigationprocessor.extensions.snake2camel
import org.apache.tools.ant.taskdefs.condition.IsLastModified
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.transform.InputArtifact
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.*
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import javax.lang.model.element.Modifier
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult


abstract class NavigationTask : DefaultTask() {



@InputFiles
abstract fun getNavFiles(): ConfigurableFileCollection

private var outputDir:File? = null
@get:OutputDirectory
lateinit var javaDir: File

@OutputDirectory
open fun getOutputDir(): File? {
return outputDir
}
@get:OutputDirectory
lateinit var resDir: File

private lateinit var android: BaseAppModuleExtension
private lateinit var packageName: String
private lateinit var mVariantName: String
@get:OutputFile
lateinit var ruleFile: File

private lateinit var mDir: File
private lateinit var mClass: TypeSpec.Builder
private val mMaps = mutableMapOf<String, String>()

/**
* 缓存当前所有的文件名和方法名
*/
private val mFileNameWithMethodName = mutableMapOf<File, String>()
private lateinit var mResource: ClassName

@TaskAction
open fun perform() {
android = project.extensions.getByName("android") as BaseAppModuleExtension
packageName = android.defaultConfig.applicationId
val android = project.extensions.getByName("android") as BaseAppModuleExtension
val packageName = android.defaultConfig.applicationId

mVariantName = inputs.properties["variant"] as String
project.delete(javaDir.listFiles())
project.delete(resDir.listFiles())
project.delete(ruleFile)

android.applicationVariants.forEach{ variant->
val file = File(project.navigationDir(variant))
if (variant.name.equals(mVariantName)) {
mDir = file
}
}

parseFiles(packageName)

createDiscard()

crateFileNames()

}


private fun parseFiles(packageName: String?) {
mResource = ClassName.get(packageName, "R")
mClass = TypeSpec.classBuilder("NavigationManager")
//.superclass(NavGraph)
.addModifiers(Modifier.PUBLIC)
inputs.files.forEach { file ->
parseAndGenerate(file)
}


val method = MethodSpec.methodBuilder("attach")
.addParameter(NavController, "controller")
.addParameter(ClassName.INT, "navigationId")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.beginControlFlow("switch(navigationId)")



mMaps.forEach { item ->
method.addStatement("case \$T.navigation.${item.key}:", mResource)
method.addStatement("${item.value}(controller)")
mFileNameWithMethodName.forEach { item ->
method.addStatement("case \$T.navigation.${item.key.xmlName}:", mResource)
method.addStatement(" ${item.value}(controller)")
method.addStatement("break")
}
method.endControlFlow()
mClass.addMethod(method.build())

val javaFile = JavaFile.builder("$packageName.navigation", mClass.build())
.build()
if (!mDir.exists()) {
mDir.mkdirs()
if (!javaDir.exists()) {
javaDir.mkdirs()
}
javaFile.writeTo(mDir)
javaFile.writeTo(javaDir)
}


private fun parseAndGenerate(file: File) {
val name = file.xmlName.snake2camel()
val methodName = "attachNavigation$name"
mMaps[file.xmlName] = methodName
mFileNameWithMethodName[file] = methodName
val method = MethodSpec.methodBuilder(methodName)
.addParameter(NavController, "controller")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
Expand All @@ -118,7 +129,6 @@ abstract class NavigationTask : DefaultTask() {
if (attr.key.isAndroidLabel) {
method.addStatement("graph.setLabel(\"${attr.value}\")", mResource)
}
println(attr.value)
}


Expand Down Expand Up @@ -147,7 +157,8 @@ abstract class NavigationTask : DefaultTask() {
method.addStatement("fragment.setId(\$T.id.${attr.value.resId})", mResource)
}
attr.key.isAndroidName -> {
method.addStatement("fragment.setClassName(${attr.value}.class.getName())")
val type = ClassName.bestGuess(attr.value)
method.addStatement("fragment.setClassName(\$T.class.getName())", type)
}
attr.key.isAndroidLabel -> {
method.addStatement("fragment.setLabel(\"${attr.value}\")")
Expand Down Expand Up @@ -230,15 +241,60 @@ abstract class NavigationTask : DefaultTask() {
)
}

method.addStatement("action = new \$T(\$T.id.$destId,builder.build())",
NavAction, mResource)
method.addStatement("fragment.putAction(\$T.id.${actionId},action)",
mResource)
method.addStatement(
"action = new \$T(\$T.id.$destId,builder.build())",
NavAction, mResource
)
method.addStatement(
"fragment.putAction(\$T.id.${actionId},action)", mResource
)
}


private fun crateFileNames() {
ruleFile.delete()
val fw = FileWriter(ruleFile.absoluteFile)
val bw = BufferedWriter(fw)
mFileNameWithMethodName.forEach { attr ->
bw.write(attr.key.absolutePath)
bw.newLine()
}
bw.close()
}

private fun createDiscard() {
val document = DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.newDocument()

document.xmlStandalone = false

val resources = document.createElement("resources")
resources.setAttribute("xmlns:tools", "http://schemas.android.com/tools")
val discards = mutableListOf<String>()
mFileNameWithMethodName.forEach { item ->
discards.add("@navigation/${item.key.xmlName}")
}
resources.setAttribute("tools:discard", discards.joinToString(separator = ",") { it })
document.appendChild(resources)

val rawDir = File(resDir, "raw")
if (!rawDir.exists()) {
rawDir.mkdirs()
}
val discardFile = File(rawDir, "nav_discard.xml")

val transformer = TransformerFactory.newInstance().newTransformer()
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
//是否自动换行
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
transformer.transform(DOMSource(document), StreamResult(discardFile))
}


protected fun getIncremental(): Boolean {
return true
return false
}

}
Loading

0 comments on commit 15a5653

Please sign in to comment.