Skip to content
Merged
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 @@ -18,10 +18,12 @@

package com.tencent.shadow.core.gradle

import com.android.build.gradle.AppExtension
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 +38,18 @@ internal interface AGPCompat {
fun getAaptAdditionalParameters(processResourcesTask: Task): List<String>
fun getMinSdkVersion(pluginVariant: ApplicationVariant): Int
fun hasDeprecatedTransformApi(): Boolean
fun isGeneratePluginManifestByMergedManifest(
project: Project,
appExtension: AppExtension,
pluginVariant: ApplicationVariant
): 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
@@ -1,13 +1,15 @@
package com.tencent.shadow.core.gradle

import com.android.SdkConstants
import com.android.build.gradle.AppExtension
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 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 +146,147 @@ internal class AGPCompatImpl : AGPCompat {
return true
}

override fun isGeneratePluginManifestByMergedManifest(
project: Project,
appExtension: AppExtension,
pluginVariant: ApplicationVariant
): Boolean {
// 可以通过配置强制开启
if ("true" == project.findProperty("shadow.generatePluginManifestUseMergedManifest")) {
return true
}
// 没有开启无用资源删减,则不使用 merged manifest
try {
if (!pluginVariant.buildType.isMinifyEnabled) {
return false
}
// AppExtension 获取的 BuildType 无法获取 isShrinkResources 属性,只能查找原始的 BuildType 实现。
if (!appExtension.buildTypes.getByName(pluginVariant.buildType.name).isShrinkResources) {
return false
}
} catch (ignored: Error) {
}

// 开启无用资源删减功能,同时AGP 版本至少要为 8.9.0
try {
val version = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
val majorVersion = version.substringBefore('.', "0").toInt()
if (majorVersion > 8) {
return true
}
if (majorVersion == 8) {
val minorVersion =
version.substringAfter('.').substringBefore('.').toIntOrNull() ?: 0
return minorVersion >= 9
}
} catch (ignored: Error) {
}
// 默认不使用 merged manifest 。
return false
}

/**
* 获取生成最终 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,6 +26,7 @@ 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.createManifestValueParser
import com.tencent.shadow.core.manifest_parser.generatePluginManifest
import com.tencent.shadow.core.transform.DeprecatedTransformWrapper
import com.tencent.shadow.core.transform.GradleTransformWrapper
Expand Down Expand Up @@ -111,7 +112,20 @@ class ShadowPlugin : Plugin<Project> {

val appExtension: AppExtension =
project.extensions.getByType(AppExtension::class.java)
createGeneratePluginManifestTasks(project, appExtension, pluginVariant)
if (agpCompat.isGeneratePluginManifestByMergedManifest(
project,
appExtension,
pluginVariant
)
) {
createGeneratePluginManifestTasksByMergedManifest(
project,
appExtension,
pluginVariant
)
} else {
createGeneratePluginManifestTasks(project, appExtension, pluginVariant)
}
}
}

Expand Down Expand Up @@ -276,6 +290,56 @@ class ShadowPlugin : Plugin<Project> {
(javacTask as JavaCompile).source(project.fileTree(relativePath))
}

private fun createGeneratePluginManifestTasksByMergedManifest(
project: Project,
appExtension: AppExtension,
pluginVariant: ApplicationVariant
) {
val output = pluginVariant.outputs.first()

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")

val generatePluginManifestTask =
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 manifestValueParser = createManifestValueParser(rTxt)
generatePluginManifest(
mergedManifest,
pluginManifestSourceDir,
"com.tencent.shadow.core.manifest_parser",
manifestValueParser
)
}
}
javacTask.dependsOn(generatePluginManifestTask)

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

/**
* 反射apkanalyzer中的BinaryXmlParser类的decodeXml方法
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ sealed class AndroidManifestKeys {
typealias ComponentMapKey = String
typealias ComponentMapValue = Any
typealias ComponentMap = Map<ComponentMapKey, ComponentMapValue>
typealias MutableComponentMap = MutableMap<ComponentMapKey, ComponentMapValue>
typealias MutableComponentMap = MutableMap<ComponentMapKey, ComponentMapValue>
typealias ManifestValueParser = (String) -> String
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,74 @@ import java.io.File
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
* @param outputDir 生成文件的输出目录
* @param packageName 生成类的包名
* @param manifestValueParser 资源解析器
*/
fun generatePluginManifest(
xmlFile: File,
outputDir: File,
packageName: String
packageName: String,
manifestValueParser: ManifestValueParser? = null
) {
val androidManifest = AndroidManifestReader().read(xmlFile)
val generator = PluginManifestGenerator()
generator.generate(androidManifest, outputDir, packageName)
}
generator.generate(androidManifest, outputDir, packageName, manifestValueParser)
}

/**
* 创建资源解析器。
*
* @param rTxt R.txt文件
* @return 资源解析器
*/
fun createManifestValueParser(rTxt: File): ManifestValueParser {
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