Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.api.BaseVariantOutput
import com.android.build.gradle.internal.dsl.ProductFlavor
import org.gradle.api.Project
import org.gradle.api.Task
import java.io.File

Expand All @@ -36,4 +37,7 @@ internal interface AGPCompat {
fun getAaptAdditionalParameters(processResourcesTask: Task): List<String>
fun getMinSdkVersion(pluginVariant: ApplicationVariant): Int
fun hasDeprecatedTransformApi(): Boolean
fun getProcessManifestTask(output: BaseVariantOutput): Task
fun getProcessManifestFile(project: Project, pluginVariant: ApplicationVariant, output: BaseVariantOutput): File
fun getRTxtFile(project: Project, processResourcesTask: Task?, variantName: String): File
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.android.build.gradle.internal.dsl.ProductFlavor
import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.sdklib.AndroidVersion.VersionCodes
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
Expand Down Expand Up @@ -144,6 +145,108 @@ internal class AGPCompatImpl : AGPCompat {
return true
}

/**
* 获取生成最终 AndroidManifest.xml 文件的任务。
*/
override fun getProcessManifestTask(output: BaseVariantOutput): Task {
return try {
output.processManifestProvider.get()
} catch (_: Error) {
output.processManifest
}
}

/**
* 获取合并后的 AndroidManifest.xml 文件。
*
* 优先从 processManifest 任务输出获取,否则搜索 intermediates 目录。
*/
override fun getProcessManifestFile(
project: Project,
pluginVariant: ApplicationVariant,
output: BaseVariantOutput
): File {
// 1. 优先从任务输出获取
try {
output.processManifestProvider.get().outputs.files.files.forEach {
findFileByName(it, "AndroidManifest.xml")?.let { file -> return file }
}
} catch (_: Exception) {
// 忽略
}

val variantName = pluginVariant.name

// 2. 搜索中间产物目录
return listOf(
"intermediates/merged_manifests/$variantName", // AGP 4.x/7.x/8.x
"intermediates/manifests/full/$variantName", // AGP 3.x
)
.map { File(project.buildDir, it) }
.first {
findFileByName(it, "AndroidManifest.xml") != null
}
}

/**
* 获取 R.txt 文件。
*
* 优先从 processResources 任务的输出获取(最准确), 否则搜索 intermediates 目录。
*/
override fun getRTxtFile(
project: Project,
processResourcesTask: Task?,
variantName: String
): File {
// 1. 优先尝试从任务输出中查找
if (processResourcesTask != null) {
try {
processResourcesTask.outputs.files.files.forEach {
findFileByName(it, "R.txt")?.let { file -> return file }
}
} catch (_: Exception) {
// 忽略解析错误,继续走备选路径
}
}

// 2. 根据 AGP 版本已知的中间产物路径搜索
return listOf(
"intermediates/runtime_symbol_list/$variantName", // AGP 4.x/7.x/8.x
"intermediates/symbols/$variantName",
"intermediates/bundles/$variantName"
)
.map { File(project.buildDir, it) }
.first {
findFileByName(it, "R.txt") != null
}
}

/**
* 搜索指定目录下指定文件名的文件。
*
* @return 文件对象,若找不到则返回 null 。
*/
private fun findFileByName(file: File, fileName:String): File? {
if (!file.exists()) {
return null
}
if (file.isFile && file.name == fileName) {
return file
}
if (file.isDirectory) {
val subFiles = file.listFiles()
if (subFiles != null) {
for (subFile in subFiles) {
val resultFile = findFileByName(subFile, fileName)
if (resultFile != null) {
return resultFile
}
}
}
}
return null
}

companion object {
fun getStringFromProperty(x: Any?): String {
return when (x) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.ApplicationVariant
import com.android.sdklib.AndroidVersion.VersionCodes
import com.tencent.shadow.core.gradle.extensions.PackagePluginExtension
import com.tencent.shadow.core.manifest_parser.createResourceMapper
import com.tencent.shadow.core.manifest_parser.generatePluginManifest
import com.tencent.shadow.core.transform.DeprecatedTransformWrapper
import com.tencent.shadow.core.transform.GradleTransformWrapper
import com.tencent.shadow.core.transform.ShadowTransform
import com.tencent.shadow.core.transform_kit.AndroidClassPoolBuilder
import com.tencent.shadow.core.transform_kit.ClassPoolBuilder
import org.gradle.api.*
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.JavaCompile
import java.io.File
import java.net.URLClassLoader
Expand Down Expand Up @@ -103,8 +105,9 @@ class ShadowPlugin : Plugin<Project> {
initAndroidClassPoolBuilder(baseExtension, project)

createPackagePluginTasks(project)

addLocateApkanalyzerTask(project)
if (agpCompat.hasDeprecatedTransformApi()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个分支条件直接复用了transformAPI版本的判断,应该是巧合吧?这样以后可能就看不懂了。还是专门定一个判断接口吧,即便实现是一样的也没关系。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当在 AGP 8.9.0 版本中开启 shrinkResources 功能后,ap_ 文件中的 AndroidManifest.xml 为新的二进制格式

你这里复用的判断条件似乎没有对shrinkResources是否开启作判断。从你的描述来看,我们应该只对同时符合AGP版本和shrinkResources开启的场景使用这个新的功能。

addLocateApkanalyzerTask(project)
}

onEachPluginVariant(project) { pluginVariant ->
checkAaptPackageIdConfig(pluginVariant)
Expand Down Expand Up @@ -214,6 +217,77 @@ class ShadowPlugin : Plugin<Project> {
appExtension: AppExtension,
pluginVariant: ApplicationVariant
) {
val variantName = pluginVariant.name
val capitalizeVariantName = variantName.capitalize()

// 添加生成PluginManifest.java任务
val pluginManifestSourceDir =
File(project.buildDir, "generated/source/pluginManifest/$variantName")

val javacTask = project.tasks.getByName("compile${capitalizeVariantName}JavaWithJavac")

// AGP 4.0.0 不支持下面的写法,转成 ()-> TaskProvider<Task?>? 再包一层的实现。
// val func = if (agpCompat.hasDeprecatedTransformApi()) this::createGeneratePluginManifestTaskOld else this::createGeneratePluginManifestTaskNew
// val generatePluginManifestTask = func.invoke(project, pluginVariant, pluginManifestSourceDir)
val func: () -> TaskProvider<Task?>? = if (agpCompat.hasDeprecatedTransformApi()) {
{
createGeneratePluginManifestTaskOld(project, pluginVariant, pluginManifestSourceDir)
}
} else {
{
createGeneratePluginManifestTaskNew(project, pluginVariant, pluginManifestSourceDir)
}
}
val generatePluginManifestTask = func.invoke()
javacTask.dependsOn(generatePluginManifestTask)

// 把PluginManifest.java添加为源码
val relativePath =
project.projectDir.toPath().relativize(pluginManifestSourceDir.toPath()).toString()
(javacTask as JavaCompile).source(project.fileTree(relativePath))
}

private fun createGeneratePluginManifestTaskNew(
project: Project,
pluginVariant: ApplicationVariant,
pluginManifestSourceDir: File,
): TaskProvider<Task?>? {
val output = pluginVariant.outputs.first()

val variantName = pluginVariant.name
val capitalizeVariantName = variantName.capitalize()

return project.tasks.register("generate${capitalizeVariantName}PluginManifest") {
// 依赖 processManifest 任务以获取最终的 AndroidManifest.xml
val processManifestTask = agpCompat.getProcessManifestTask(output)
// 依赖 processResources 任务以获取 R.txt
val processResourcesTask = agpCompat.getProcessResourcesTask(output)
it.dependsOn(processManifestTask)
it.dependsOn(processResourcesTask)

it.outputs.dir(pluginManifestSourceDir).withPropertyName("pluginManifestSourceDir")

it.doLast {
// 解析合并后的 AndroidManifest.xml + R.txt
// 这种方案直接解析 XML 格式的 AndroidManifest.xml ,不再依赖 aapt2 产生的二进制产物
val mergedManifest = agpCompat.getProcessManifestFile(project, pluginVariant, output)
val rTxt = agpCompat.getRTxtFile(project, processResourcesTask, variantName)
val resourceMapper = createResourceMapper(rTxt)
generatePluginManifest(
mergedManifest,
pluginManifestSourceDir,
"com.tencent.shadow.core.manifest_parser",
resourceMapper
)
}
}
}

private fun createGeneratePluginManifestTaskOld(
project: Project,
pluginVariant: ApplicationVariant,
pluginManifestSourceDir: File,
): TaskProvider<Task?>? {
val output = pluginVariant.outputs.first()

val variantName = pluginVariant.name
Expand Down Expand Up @@ -249,12 +323,7 @@ class ShadowPlugin : Plugin<Project> {
}
}


// 添加生成PluginManifest.java任务
val pluginManifestSourceDir =
File(project.buildDir, "generated/source/pluginManifest/$variantName")
val generatePluginManifestTask =
project.tasks.register("generate${capitalizeVariantName}PluginManifest") {
return project.tasks.register("generate${capitalizeVariantName}PluginManifest") {
it.dependsOn(decodeBinaryManifestTask)
it.inputs.file(decodeXml)
it.outputs.dir(pluginManifestSourceDir).withPropertyName("pluginManifestSourceDir")
Expand All @@ -267,13 +336,6 @@ class ShadowPlugin : Plugin<Project> {
)
}
}
val javacTask = project.tasks.getByName("compile${capitalizeVariantName}JavaWithJavac")
javacTask.dependsOn(generatePluginManifestTask)

// 把PluginManifest.java添加为源码
val relativePath =
project.projectDir.toPath().relativize(pluginManifestSourceDir.toPath()).toString()
(javacTask as JavaCompile).source(project.fileTree(relativePath))
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.tencent.shadow.core.manifest_parser

import java.io.File
import java.util.Collections

/**
* manifest-parser的入口方法
Expand All @@ -9,13 +10,73 @@ import java.io.File
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
* @param outputDir 生成文件的输出目录
* @param packageName 生成类的包名
* @param resourceMapper 资源映射器
*/
fun generatePluginManifest(
xmlFile: File,
outputDir: File,
packageName: String
packageName: String,
resourceMapper: ((String) -> String)? = null
) {
val androidManifest = AndroidManifestReader().read(xmlFile)
val generator = PluginManifestGenerator()
generator.generate(androidManifest, outputDir, packageName)
}
generator.generate(androidManifest, outputDir, packageName, resourceMapper)
}

/**
* 创建资源映射器。
*
* @param rTxt R.txt文件
* @return 资源映射器
*/
fun createResourceMapper(rTxt: File): (String) -> String {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个mapper后面用了多处,应该定义一个typealias

val rTxtMap = parseRTxt(rTxt)

return { resName ->
if (resName.startsWith("@android:")) {
// @android:style/Theme.NoTitleBar -> android.R.style.Theme_NoTitleBar
val parts = resName.substringAfter("@android:").split("/")
val type = parts[0]
val name = parts[1].replace(".", "_")
"android.R.$type.$name"
} else {
// @[package:]type/name -> id 值
var raw = resName.substringAfter("@")
if (raw.contains(":")) {
raw = raw.substringAfter(":")
}
val parts = raw.split("/")
val type = parts[0]
val name = parts[1].replace('.', '_')
val key = "@$type/$name"
rTxtMap[key] ?: throw IllegalArgumentException("Resource not found in R.txt: $resName (normalized: $key)")
}
}
}

/**
* 解析 R.txt 文件并生成资源 ID 映射表。 R.txt 包含项目引用的所有资源 ID。
*
* @param rTxtFile R.txt 文件对象
* @return 资源全称(如 @string/app_name)到 ID 的映射
*/
fun parseRTxt(rTxtFile: File): Map<String, String> {
if (!rTxtFile.exists()) return Collections.emptyMap()

val map = mutableMapOf<String, String>()
rTxtFile.useLines {
it.forEach { line ->
if (!(line.startsWith("int "))) {
return@forEach
}
val parts = line.split(Regex("\\s+")).filter { it.isNotBlank() }
if (parts.size == 4 && parts[0] == "int") {
val type = parts[1]
val name = parts[2]
val idStr = parts[3]
map["@$type/$name"] = idStr
}
}
}
return map
}
Loading