All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
main.com.xml.guard.tasks.XmlClassGuardTask.kt Maven / Gradle / Ivy
package com.xml.guard.tasks
import com.xml.guard.entensions.GuardExtension
import com.xml.guard.model.ClassInfo
import com.xml.guard.model.MappingParser
import com.xml.guard.utils.*
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import java.io.File
import javax.inject.Inject
/**
* User: ljx
* Date: 2022/2/25
* Time: 19:06
*/
open class XmlClassGuardTask @Inject constructor(
private val guardExtension: GuardExtension,
private val variantName: String,
) : DefaultTask() {
init {
group = "guard"
}
private val mappingFile by lazy { project.file(MappingParser.MAPPING_XML_CLASS) }
private val mapping by lazy {
MappingParser.parseXmlClass(
project,
guardExtension,
mappingFile,
isComparison = true
)
}
private val hasNavigationPlugin by lazy { project.plugins.hasPlugin("androidx.navigation.safeargs") }
private val fragmentDirectionList by lazy { mutableListOf() }
@TaskAction
fun execute() {
val androidProjects = allDependencyAndroidProjects(project)
// 1、遍历res下的xml文件,找到自定义的类(View/Fragment/四大组件等),并将混淆结果同步到xml文件内
androidProjects.forEach { handleResDir(it) }
// 2、混淆其余类文件目录
mapping.obfuscateOtherDir(androidProjects, variantName)
// 3、仅修改文件名及文件路径,返回本次修改的文件
val classMapping = mapping.obfuscateAllClass(project, variantName)
if (hasNavigationPlugin && fragmentDirectionList.isNotEmpty()) {
fragmentDirectionList.forEach {
if (mapping.isClassObfuscatedOrWhiteList(it)) return@forEach
val directions = "Directions"
classMapping["${it}${directions}"] = "${classMapping[it]}${directions}"
}
}
// 4、替换 Java/kotlin 文件里引用到的类
if (classMapping.isNotEmpty()) {
androidProjects.forEach { replaceJavaText(it, classMapping) }
}
// 5、混淆映射写出到文件
mapping.writeMappingToFile(mappingFile)
}
// 处理res目录
private fun handleResDir(project: Project) {
val packageName = project.findPackage()
// 过滤res目录下的layout、navigation、xml目录
val xmlDirs = project.findXmlDirs(variantName, "layout", "navigation", "xml")
xmlDirs.add(project.manifestFile())
project.files(xmlDirs).asFileTree.forEach { xmlFile ->
guardXml(project, xmlFile, packageName)
}
}
private fun guardXml(project: Project, xmlFile: File, packageName: String) {
var xmlText = xmlFile.readText()
val classInfoList = mutableListOf()
val parentName = xmlFile.parentFile.name
when {
parentName.startsWith("navigation") -> {
findFragmentInfoList(xmlText).let { classInfoList.addAll(it) }
}
listOf("layout", "xml").any { parentName.startsWith(it) } -> {
findClassByLayoutXml(xmlText, packageName).let { classInfoList.addAll(it) }
}
xmlFile.isAndroidManifest -> {
findClassByManifest(xmlText, packageName).let { classInfoList.addAll(it) }
}
}
if (hasNavigationPlugin) {
classInfoList.mapNotNullTo(fragmentDirectionList) {
if (it.hasAction) it.classPath else null
}
}
for (classInfo in classInfoList) {
val classPath = classInfo.classPath
val dirPath = classPath.getDirPath()
// 本地不存在这个文件
val locationProject = project.findLocationProject(dirPath, variantName) ?: continue
// 已经混淆了这个类
if (mapping.isClassObfuscatedOrWhiteList(classPath)) continue
val obfuscatePath = mapping.obfuscatePath(locationProject, classPath)
xmlText = xmlText.replaceWords(classPath, obfuscatePath)
if (classPath.startsWith(packageName)) {
xmlText =
xmlText.replaceWords(classPath.substring(packageName.length), obfuscatePath)
}
if (classInfo.fromImportNode) {
var classStartIndex = classPath.indexOfLast { it == '.' }
if (classStartIndex == -1) continue
val rawClassName = classPath.substring(classStartIndex + 1)
classStartIndex = obfuscatePath.indexOfLast { it == '.' }
if (classStartIndex == -1) continue
val obfuscateClassName = obfuscatePath.substring(classStartIndex + 1)
xmlText = xmlText.replaceWords("${rawClassName}.", "${obfuscateClassName}.")
}
}
xmlFile.writeText(xmlText)
}
private fun replaceJavaText(project: Project, mapping: Map) {
val dirs = mutableListOf()
dirs.addAll(project.javaDirs(variantName))
dirs.addAll(project.aidlDirs(variantName))
// 遍历所有Java\Kt文件,替换混淆后的类的引用,import及new对象的地方
project.files(dirs).asFileTree.forEach { javaFile ->
var replaceText = javaFile.readText()
mapping.forEach {
replaceText = replaceText(javaFile, replaceText, it.key, it.value)
}
javaFile.writeText(replaceText)
}
}
private fun replaceText(
rawFile: File,
rawText: String,
rawPath: String,
obfuscatePath: String,
): String {
val rawIndex = rawPath.lastIndexOf(".")
val rawPackage = rawPath.substring(0, rawIndex)
val rawName = rawPath.substring(rawIndex + 1)
val obfuscateIndex = obfuscatePath.lastIndexOf(".")
val obfuscatePackage = obfuscatePath.substring(0, obfuscateIndex)
val obfuscateName = obfuscatePath.substring(obfuscateIndex + 1)
var replaceText = rawText
when {
rawFile.absolutePath.removeSuffix()
.endsWith(obfuscatePath.replace(".", File.separator)) -> {
// 对于自己,替换package语句及类名即可
replaceText = replaceText
.replaceWords("package $rawPackage", "package $obfuscatePackage")
.replaceWords(rawPath, obfuscatePath)
.replaceWords(rawName, obfuscateName)
}
rawFile.parent.endsWith(obfuscatePackage.replace(".", File.separator)) -> {
// 同一包下的类,原则上替换类名即可,但考虑到会依赖同包下类的内部类,所以也需要替换包名+类名
replaceText = replaceText.replaceWords(rawPath, obfuscatePath) //替换{包名+类名}
.replaceWords(rawName, obfuscateName)
}
else -> {
replaceText = replaceText.replaceWords(rawPath, obfuscatePath) //替换{包名+类名}
.replaceWords("$rawPackage.*", "$obfuscatePackage.*")
// 替换成功或已替换
if (replaceText != rawText || replaceText.contains("$obfuscatePackage.*")) {
//rawFile 文件内有引用 rawName 类,则需要替换类名
replaceText = replaceText.replaceWords(rawName, obfuscateName)
}
}
}
return replaceText
}
}