Skip to content

Commit 1a87351

Browse files
committed
feat(core.gradle-plugin): 支持AGP 8后开启shrinkResources
当在 AGP 8.9.0 版本中开启 shrinkResources 功能后,ap_ 文件中的 AndroidManifest.xml 为新的二进制格式,且资源文件引用也不会改为 ID 。新的方案使用合并后的 AndroidManifest.xml 和 R.txt 生成 PluginManifest 文件。 新的方案自动应用在 AGP 8 且开启shrinkResources时。
1 parent f9dc394 commit 1a87351

7 files changed

Lines changed: 363 additions & 32 deletions

File tree

projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompat.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.android.build.gradle.BaseExtension
2222
import com.android.build.gradle.api.ApplicationVariant
2323
import com.android.build.gradle.api.BaseVariantOutput
2424
import com.android.build.gradle.internal.dsl.ProductFlavor
25+
import org.gradle.api.Project
2526
import org.gradle.api.Task
2627
import java.io.File
2728

@@ -36,4 +37,7 @@ internal interface AGPCompat {
3637
fun getAaptAdditionalParameters(processResourcesTask: Task): List<String>
3738
fun getMinSdkVersion(pluginVariant: ApplicationVariant): Int
3839
fun hasDeprecatedTransformApi(): Boolean
40+
fun getProcessManifestTask(output: BaseVariantOutput): Task
41+
fun getProcessManifestFile(project: Project, pluginVariant: ApplicationVariant, output: BaseVariantOutput): File
42+
fun getRTxtFile(project: Project, processResourcesTask: Task?, variantName: String): File
3943
}

projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompatImpl.kt

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.android.build.gradle.internal.dsl.ProductFlavor
88
import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask
99
import com.android.build.gradle.internal.scope.InternalArtifactType
1010
import com.android.sdklib.AndroidVersion.VersionCodes
11+
import org.gradle.api.Project
1112
import org.gradle.api.Task
1213
import org.gradle.api.file.Directory
1314
import org.gradle.api.file.DirectoryProperty
@@ -144,6 +145,108 @@ internal class AGPCompatImpl : AGPCompat {
144145
return true
145146
}
146147

148+
/**
149+
* 获取生成最终 AndroidManifest.xml 文件的任务。
150+
*/
151+
override fun getProcessManifestTask(output: BaseVariantOutput): Task {
152+
return try {
153+
output.processManifestProvider.get()
154+
} catch (_: Error) {
155+
output.processManifest
156+
}
157+
}
158+
159+
/**
160+
* 获取合并后的 AndroidManifest.xml 文件。
161+
*
162+
* 优先从 processManifest 任务输出获取,否则搜索 intermediates 目录。
163+
*/
164+
override fun getProcessManifestFile(
165+
project: Project,
166+
pluginVariant: ApplicationVariant,
167+
output: BaseVariantOutput
168+
): File {
169+
// 1. 优先从任务输出获取
170+
try {
171+
output.processManifestProvider.get().outputs.files.files.forEach {
172+
findFileByName(it, "AndroidManifest.xml")?.let { file -> return file }
173+
}
174+
} catch (_: Exception) {
175+
// 忽略
176+
}
177+
178+
val variantName = pluginVariant.name
179+
180+
// 2. 搜索中间产物目录
181+
return listOf(
182+
"intermediates/merged_manifests/$variantName", // AGP 4.x/7.x/8.x
183+
"intermediates/manifests/full/$variantName", // AGP 3.x
184+
)
185+
.map { File(project.buildDir, it) }
186+
.first {
187+
findFileByName(it, "AndroidManifest.xml") != null
188+
}
189+
}
190+
191+
/**
192+
* 获取 R.txt 文件。
193+
*
194+
* 优先从 processResources 任务的输出获取(最准确), 否则搜索 intermediates 目录。
195+
*/
196+
override fun getRTxtFile(
197+
project: Project,
198+
processResourcesTask: Task?,
199+
variantName: String
200+
): File {
201+
// 1. 优先尝试从任务输出中查找
202+
if (processResourcesTask != null) {
203+
try {
204+
processResourcesTask.outputs.files.files.forEach {
205+
findFileByName(it, "R.txt")?.let { file -> return file }
206+
}
207+
} catch (_: Exception) {
208+
// 忽略解析错误,继续走备选路径
209+
}
210+
}
211+
212+
// 2. 根据 AGP 版本已知的中间产物路径搜索
213+
return listOf(
214+
"intermediates/runtime_symbol_list/$variantName", // AGP 4.x/7.x/8.x
215+
"intermediates/symbols/$variantName",
216+
"intermediates/bundles/$variantName"
217+
)
218+
.map { File(project.buildDir, it) }
219+
.first {
220+
findFileByName(it, "R.txt") != null
221+
}
222+
}
223+
224+
/**
225+
* 搜索指定目录下指定文件名的文件。
226+
*
227+
* @return 文件对象,若找不到则返回 null 。
228+
*/
229+
private fun findFileByName(file: File, fileName:String): File? {
230+
if (!file.exists()) {
231+
return null
232+
}
233+
if (file.isFile && file.name == fileName) {
234+
return file
235+
}
236+
if (file.isDirectory) {
237+
val subFiles = file.listFiles()
238+
if (subFiles != null) {
239+
for (subFile in subFiles) {
240+
val resultFile = findFileByName(subFile, fileName)
241+
if (resultFile != null) {
242+
return resultFile
243+
}
244+
}
245+
}
246+
}
247+
return null
248+
}
249+
147250
companion object {
148251
fun getStringFromProperty(x: Any?): String {
149252
return when (x) {

projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ import com.android.build.gradle.BaseExtension
2626
import com.android.build.gradle.api.ApplicationVariant
2727
import com.android.sdklib.AndroidVersion.VersionCodes
2828
import com.tencent.shadow.core.gradle.extensions.PackagePluginExtension
29+
import com.tencent.shadow.core.manifest_parser.createResourceMapper
2930
import com.tencent.shadow.core.manifest_parser.generatePluginManifest
3031
import com.tencent.shadow.core.transform.DeprecatedTransformWrapper
3132
import com.tencent.shadow.core.transform.GradleTransformWrapper
3233
import com.tencent.shadow.core.transform.ShadowTransform
3334
import com.tencent.shadow.core.transform_kit.AndroidClassPoolBuilder
3435
import com.tencent.shadow.core.transform_kit.ClassPoolBuilder
3536
import org.gradle.api.*
37+
import org.gradle.api.tasks.TaskProvider
3638
import org.gradle.api.tasks.compile.JavaCompile
3739
import java.io.File
3840
import java.net.URLClassLoader
@@ -103,8 +105,9 @@ class ShadowPlugin : Plugin<Project> {
103105
initAndroidClassPoolBuilder(baseExtension, project)
104106

105107
createPackagePluginTasks(project)
106-
107-
addLocateApkanalyzerTask(project)
108+
if (agpCompat.hasDeprecatedTransformApi()) {
109+
addLocateApkanalyzerTask(project)
110+
}
108111

109112
onEachPluginVariant(project) { pluginVariant ->
110113
checkAaptPackageIdConfig(pluginVariant)
@@ -214,6 +217,77 @@ class ShadowPlugin : Plugin<Project> {
214217
appExtension: AppExtension,
215218
pluginVariant: ApplicationVariant
216219
) {
220+
val variantName = pluginVariant.name
221+
val capitalizeVariantName = variantName.capitalize()
222+
223+
// 添加生成PluginManifest.java任务
224+
val pluginManifestSourceDir =
225+
File(project.buildDir, "generated/source/pluginManifest/$variantName")
226+
227+
val javacTask = project.tasks.getByName("compile${capitalizeVariantName}JavaWithJavac")
228+
229+
// AGP 4.0.0 不支持下面的写法,转成 ()-> TaskProvider<Task?>? 再包一层的实现。
230+
// val func = if (agpCompat.hasDeprecatedTransformApi()) this::createGeneratePluginManifestTaskOld else this::createGeneratePluginManifestTaskNew
231+
// val generatePluginManifestTask = func.invoke(project, pluginVariant, pluginManifestSourceDir)
232+
val func: () -> TaskProvider<Task?>? = if (agpCompat.hasDeprecatedTransformApi()) {
233+
{
234+
createGeneratePluginManifestTaskOld(project, pluginVariant, pluginManifestSourceDir)
235+
}
236+
} else {
237+
{
238+
createGeneratePluginManifestTaskNew(project, pluginVariant, pluginManifestSourceDir)
239+
}
240+
}
241+
val generatePluginManifestTask = func.invoke()
242+
javacTask.dependsOn(generatePluginManifestTask)
243+
244+
// 把PluginManifest.java添加为源码
245+
val relativePath =
246+
project.projectDir.toPath().relativize(pluginManifestSourceDir.toPath()).toString()
247+
(javacTask as JavaCompile).source(project.fileTree(relativePath))
248+
}
249+
250+
private fun createGeneratePluginManifestTaskNew(
251+
project: Project,
252+
pluginVariant: ApplicationVariant,
253+
pluginManifestSourceDir: File,
254+
): TaskProvider<Task?>? {
255+
val output = pluginVariant.outputs.first()
256+
257+
val variantName = pluginVariant.name
258+
val capitalizeVariantName = variantName.capitalize()
259+
260+
return project.tasks.register("generate${capitalizeVariantName}PluginManifest") {
261+
// 依赖 processManifest 任务以获取最终的 AndroidManifest.xml
262+
val processManifestTask = agpCompat.getProcessManifestTask(output)
263+
// 依赖 processResources 任务以获取 R.txt
264+
val processResourcesTask = agpCompat.getProcessResourcesTask(output)
265+
it.dependsOn(processManifestTask)
266+
it.dependsOn(processResourcesTask)
267+
268+
it.outputs.dir(pluginManifestSourceDir).withPropertyName("pluginManifestSourceDir")
269+
270+
it.doLast {
271+
// 解析合并后的 AndroidManifest.xml + R.txt
272+
// 这种方案直接解析 XML 格式的 AndroidManifest.xml ,不再依赖 aapt2 产生的二进制产物
273+
val mergedManifest = agpCompat.getProcessManifestFile(project, pluginVariant, output)
274+
val rTxt = agpCompat.getRTxtFile(project, processResourcesTask, variantName)
275+
val resourceMapper = createResourceMapper(rTxt)
276+
generatePluginManifest(
277+
mergedManifest,
278+
pluginManifestSourceDir,
279+
"com.tencent.shadow.core.manifest_parser",
280+
resourceMapper
281+
)
282+
}
283+
}
284+
}
285+
286+
private fun createGeneratePluginManifestTaskOld(
287+
project: Project,
288+
pluginVariant: ApplicationVariant,
289+
pluginManifestSourceDir: File,
290+
): TaskProvider<Task?>? {
217291
val output = pluginVariant.outputs.first()
218292

219293
val variantName = pluginVariant.name
@@ -249,12 +323,7 @@ class ShadowPlugin : Plugin<Project> {
249323
}
250324
}
251325

252-
253-
// 添加生成PluginManifest.java任务
254-
val pluginManifestSourceDir =
255-
File(project.buildDir, "generated/source/pluginManifest/$variantName")
256-
val generatePluginManifestTask =
257-
project.tasks.register("generate${capitalizeVariantName}PluginManifest") {
326+
return project.tasks.register("generate${capitalizeVariantName}PluginManifest") {
258327
it.dependsOn(decodeBinaryManifestTask)
259328
it.inputs.file(decodeXml)
260329
it.outputs.dir(pluginManifestSourceDir).withPropertyName("pluginManifestSourceDir")
@@ -267,13 +336,6 @@ class ShadowPlugin : Plugin<Project> {
267336
)
268337
}
269338
}
270-
val javacTask = project.tasks.getByName("compile${capitalizeVariantName}JavaWithJavac")
271-
javacTask.dependsOn(generatePluginManifestTask)
272-
273-
// 把PluginManifest.java添加为源码
274-
val relativePath =
275-
project.projectDir.toPath().relativize(pluginManifestSourceDir.toPath()).toString()
276-
(javacTask as JavaCompile).source(project.fileTree(relativePath))
277339
}
278340

279341
/**
Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.tencent.shadow.core.manifest_parser
22

33
import java.io.File
4+
import java.util.Collections
45

56
/**
67
* manifest-parser的入口方法
@@ -9,13 +10,73 @@ import java.io.File
910
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
1011
* @param outputDir 生成文件的输出目录
1112
* @param packageName 生成类的包名
13+
* @param resourceMapper 资源映射器
1214
*/
1315
fun generatePluginManifest(
1416
xmlFile: File,
1517
outputDir: File,
16-
packageName: String
18+
packageName: String,
19+
resourceMapper: ((String) -> String)? = null
1720
) {
1821
val androidManifest = AndroidManifestReader().read(xmlFile)
1922
val generator = PluginManifestGenerator()
20-
generator.generate(androidManifest, outputDir, packageName)
21-
}
23+
generator.generate(androidManifest, outputDir, packageName, resourceMapper)
24+
}
25+
26+
/**
27+
* 创建资源映射器。
28+
*
29+
* @param rTxt R.txt文件
30+
* @return 资源映射器
31+
*/
32+
fun createResourceMapper(rTxt: File): (String) -> String {
33+
val rTxtMap = parseRTxt(rTxt)
34+
35+
return { resName ->
36+
if (resName.startsWith("@android:")) {
37+
// @android:style/Theme.NoTitleBar -> android.R.style.Theme_NoTitleBar
38+
val parts = resName.substringAfter("@android:").split("/")
39+
val type = parts[0]
40+
val name = parts[1].replace(".", "_")
41+
"android.R.$type.$name"
42+
} else {
43+
// @[package:]type/name -> id 值
44+
var raw = resName.substringAfter("@")
45+
if (raw.contains(":")) {
46+
raw = raw.substringAfter(":")
47+
}
48+
val parts = raw.split("/")
49+
val type = parts[0]
50+
val name = parts[1].replace('.', '_')
51+
val key = "@$type/$name"
52+
rTxtMap[key] ?: throw IllegalArgumentException("Resource not found in R.txt: $resName (normalized: $key)")
53+
}
54+
}
55+
}
56+
57+
/**
58+
* 解析 R.txt 文件并生成资源 ID 映射表。 R.txt 包含项目引用的所有资源 ID。
59+
*
60+
* @param rTxtFile R.txt 文件对象
61+
* @return 资源全称(如 @string/app_name)到 ID 的映射
62+
*/
63+
fun parseRTxt(rTxtFile: File): Map<String, String> {
64+
if (!rTxtFile.exists()) return Collections.emptyMap()
65+
66+
val map = mutableMapOf<String, String>()
67+
rTxtFile.useLines {
68+
it.forEach { line ->
69+
if (!(line.startsWith("int "))) {
70+
return@forEach
71+
}
72+
val parts = line.split(Regex("\\s+")).filter { it.isNotBlank() }
73+
if (parts.size == 4 && parts[0] == "int") {
74+
val type = parts[1]
75+
val name = parts[2]
76+
val idStr = parts[3]
77+
map["@$type/$name"] = idStr
78+
}
79+
}
80+
}
81+
return map
82+
}

0 commit comments

Comments
 (0)