Skip to content

Commit

Permalink
feat(core): 支持android.nonTransitiveRClass=true
Browse files Browse the repository at this point in the history
从AGP 8.0.0开始nonTransitiveRClass已成默认特性。nonTransitiveRClass开启后,各aar中的资源id
不会被重复添加到apk模块的R类中。因此原本方案中在资源变量名前统统拼接apk模块包名的R类,
以取得资源id实际值的做法已经不可行。

manifest_parser改为依赖`apkanalyzer manifest print`输出的已注入资源id值的manifest。
这样就避免了原本依赖拼接apk模块包名下的R类到资源名前面的实现方案。

fix #1041
#1127
  • Loading branch information
shifujun committed Dec 10, 2022
1 parent feb4b7f commit a30a15c
Show file tree
Hide file tree
Showing 16 changed files with 156 additions and 283 deletions.
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ org.gradle.jvmargs=-Xmx4096m
android.useAndroidX=true
org.gradle.caching=true

android.nonTransitiveRClass=true
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

<application
android:icon="@android:drawable/sym_def_app_icon"
android:theme="@android:style/Theme.NoTitleBar"
android:label="@string/app_name">
<activity
android:name="com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity"
android:theme="@style/CustomActivityTheme"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTask">
<intent-filter>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="CustomActivityTheme" parent="@android:style/Theme.NoTitleBar"></style>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tencent.shadow.sample.plugin.app.lib.base.plugin">

<application android:name="com.tencent.shadow.sample.plugin.app.lib.gallery.TestApplication" />
<application
android:name="com.tencent.shadow.sample.plugin.app.lib.gallery.TestApplication"
android:theme="@style/CustomActivityTheme" />
</manifest>

Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,14 @@ 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

/**
* 不同版本AGP的兼容层
*/
internal interface AGPCompat {
fun getManifestFile(processManifestTask: Task): File
fun getPackageForR(project: Project, variantName: String): String
fun addFlavorDimension(baseExtension: BaseExtension, dimensionName: String)
fun setProductFlavorDefault(productFlavor: ProductFlavor, isDefault: Boolean)
fun getProcessManifestTask(output: BaseVariantOutput): Task
fun getProcessResourcesTask(output: BaseVariantOutput): Task
fun getAaptAdditionalParameters(processResourcesTask: Task): List<String>
fun getMinSdkVersion(pluginVariant: ApplicationVariant): Int
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
package com.tencent.shadow.core.gradle

import com.android.SdkConstants
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.tasks.ProcessApplicationManifest
import com.android.build.gradle.tasks.ProcessMultiApkApplicationManifest
import com.android.sdklib.AndroidVersion.VersionCodes
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Property
import java.io.File

internal class AGPCompatImpl : AGPCompat {

override fun getProcessManifestTask(output: BaseVariantOutput): Task =
try {
output.processManifestProvider.get()
} catch (e: NoSuchMethodError) {
output.processManifest
}

override fun getProcessResourcesTask(output: BaseVariantOutput): Task =
try {
output.processResourcesProvider.get()
Expand Down Expand Up @@ -57,58 +45,6 @@ internal class AGPCompatImpl : AGPCompat {
additionalParameters ?: listOf()
}


override fun getManifestFile(processManifestTask: Task) =
when (processManifestTask.javaClass.superclass.simpleName) {
"ProcessMultiApkApplicationManifest" -> {
(processManifestTask as ProcessMultiApkApplicationManifest)
.mainMergedManifest.get().asFile
}
"ProcessApplicationManifest" -> {
try {
(processManifestTask as ProcessApplicationManifest)
.mergedManifest.get().asFile
} catch (e: NoSuchMethodError) {
//AGP小于4.1.0
val dir =
processManifestTask.outputs.files.files
.first { it.parentFile.name == "merged_manifests" }
File(dir, SdkConstants.ANDROID_MANIFEST_XML)
}
}
"MergeManifests" -> {
val dir = try {// AGP 3.2.0
processManifestTask.outputs.files.files
.first { it.parentFile.parentFile.parentFile.name == "merged_manifests" }
} catch (e: NoSuchElementException) {
// AGP 3.1.0
processManifestTask.outputs.files.files
.first { it.path.contains("intermediates${File.separator}manifests${File.separator}full${File.separator}") }
}
File(dir, SdkConstants.ANDROID_MANIFEST_XML)
}
else -> throw IllegalStateException("不支持的Task类型:${processManifestTask.javaClass}")
}

override fun getPackageForR(project: Project, variantName: String): String {
val linkApplicationAndroidResourcesTask =
project.tasks.getByName("process${variantName.capitalize()}Resources")
return getStringFromProperty(
when {
linkApplicationAndroidResourcesTask.hasProperty("namespace") -> {
linkApplicationAndroidResourcesTask.property("namespace")
}
linkApplicationAndroidResourcesTask.hasProperty("originalApplicationId") -> {
linkApplicationAndroidResourcesTask.property("originalApplicationId")
}
linkApplicationAndroidResourcesTask.hasProperty("packageName") -> {
linkApplicationAndroidResourcesTask.property("packageName")
}
else -> throw IllegalStateException("不支持的AGP版本")
}
)
}

override fun addFlavorDimension(baseExtension: BaseExtension, dimensionName: String) {
val flavorDimensionList = baseExtension.flavorDimensionList
as MutableList<String>? // AGP 3.6.0版本可能返回null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import com.tencent.shadow.core.transform_kit.ClassPoolBuilder
import org.gradle.api.*
import org.gradle.api.tasks.compile.JavaCompile
import java.io.File
import java.net.URLClassLoader
import java.util.zip.ZipFile

class ShadowPlugin : Plugin<Project> {

Expand Down Expand Up @@ -65,6 +67,8 @@ class ShadowPlugin : Plugin<Project> {

createPackagePluginTasks(project)

addLocateApkanalyzerTask(project)

onEachPluginVariant(project) { pluginVariant ->
checkAaptPackageIdConfig(pluginVariant)

Expand All @@ -77,6 +81,44 @@ class ShadowPlugin : Plugin<Project> {
checkKotlinAndroidPluginForPluginManifestTask(project)
}

private fun addLocateApkanalyzerTask(project: Project) {
val appExtension: AppExtension =
project.extensions.getByType(AppExtension::class.java)
val sdkDirectory = appExtension.sdkDirectory
val outputFile = project.locateApkanalyzerResultPath()

project.tasks.register(locateApkanalyzerTaskName) {
it.inputs.property("sdkPath", sdkDirectory.path)
it.outputs.file(outputFile).withPropertyName("locateApkanalyzerResultPath")

it.doLast {
// 如果其他project的此任务执行过了,就不用再查找了
if (outputFile.exists() && File(outputFile.readText()).exists()) {
return@doLast
}

// 找出apkanalyzer.jar.它是build tool的一部分,但位置随着版本有变化,所以这里用搜索文件确定位置
// 如果有多个版本,随机取第一个,因为只用decodeXml方法,预期不同版本没什么区别。
val apkanalyzerJarFile =
try {
sdkDirectory.walk().filter { it.name.equals("apkanalyzer.jar") }
.first()
} catch (e: NoSuchElementException) {
// https://developer.android.com/studio/command-line/apkanalyzer
// https://developer.android.com/studio/releases/sdk-tools
throw Error(
"找不到apkanalyzer.jar.它来自:" +
"SDK Tools, Revision 26.1.1 (September 2017)," +
"如果高版本SDK也找不到这个文件,Shadow就需要更新了。"
)
}

outputFile.parentFile.mkdirs()
outputFile.writeText(apkanalyzerJarFile.absolutePath)
}
}
}

/**
* GeneratePluginManifestTask会向android DSL添加新的java源码目录,
* 而kotlin-android会在syncKotlinAndAndroidSourceSets中接管java的源码目录,
Expand Down Expand Up @@ -124,41 +166,88 @@ class ShadowPlugin : Plugin<Project> {
/**
* 创建生成PluginManifest.java的任务
*/
@Suppress("PrivateApi")// for use BinaryXmlParser(apkanalyzer)
private fun createGeneratePluginManifestTasks(
project: Project,
appExtension: AppExtension,
pluginVariant: ApplicationVariant
) {
val output = pluginVariant.outputs.first()

val processManifestTask = agpCompat.getProcessManifestTask(output)
val manifestFile = agpCompat.getManifestFile(processManifestTask)
val variantName = pluginVariant.name
val outputDir = File(project.buildDir, "generated/source/pluginManifest/$variantName")
val capitalizeVariantName = variantName.capitalize()

// 找出ap_文件
val processResourcesTask = agpCompat.getProcessResourcesTask(output)
val processedResFile = File(
processResourcesTask.outputs.files.files.first { it.name.equals("out") },
"resources-$variantName.ap_"
)

// decodeBinaryManifestTask输出的apkanalyzer manifest print结果文件
val decodeXml = File(
project.buildDir,
"intermediates/decodeBinaryManifest/$variantName/AndroidManifest.xml"
)

// 添加decodeXml任务
val decodeBinaryManifestTask =
project.tasks.register("decode${capitalizeVariantName}BinaryManifest") {
it.dependsOn(locateApkanalyzerTaskName)
it.dependsOn(processResourcesTask)
it.inputs.file(processedResFile)
it.outputs.file(decodeXml).withPropertyName("decodeXml")

it.doLast {
val jarPath = File(project.locateApkanalyzerResultPath().readText())
val tempCL = URLClassLoader(arrayOf(jarPath.toURL()), contextClassLoader)
val binaryXmlParserClass =
tempCL.loadClass("com.android.tools.apk.analyzer.BinaryXmlParser")
val decodeXmlMethod = binaryXmlParserClass.getDeclaredMethod(
"decodeXml",
String::class.java,
ByteArray::class.java
)

val zipFile = ZipFile(processedResFile)
val binaryXml = zipFile.getInputStream(
zipFile.getEntry("AndroidManifest.xml")
).readBytes()

val outputXmlBytes = decodeXmlMethod.invoke(
null,
"AndroidManifest.xml",
binaryXml
) as ByteArray
decodeXml.parentFile.mkdirs()
decodeXml.writeBytes(outputXmlBytes)
}
}


// 添加生成PluginManifest.java任务
val pluginManifestSourceDir =
File(project.buildDir, "generated/source/pluginManifest/$variantName")
val generatePluginManifestTask =
project.tasks.register("generate${variantName.capitalize()}PluginManifest") {
it.dependsOn(processManifestTask)
it.inputs.file(manifestFile)
it.outputs.dir(outputDir).withPropertyName("outputDir")

val packageForR = agpCompat.getPackageForR(project, variantName)
project.tasks.register("generate${capitalizeVariantName}PluginManifest") {
it.dependsOn(decodeBinaryManifestTask)
it.inputs.file(decodeXml)
it.outputs.dir(pluginManifestSourceDir).withPropertyName("pluginManifestSourceDir")

it.doLast {
generatePluginManifest(
manifestFile,
outputDir,
"com.tencent.shadow.core.manifest_parser",
packageForR
decodeXml,
pluginManifestSourceDir,
"com.tencent.shadow.core.manifest_parser"
)
}
}
val javacTask = project.tasks.getByName("compile${variantName.capitalize()}JavaWithJavac")
val javacTask = project.tasks.getByName("compile${capitalizeVariantName}JavaWithJavac")
javacTask.dependsOn(generatePluginManifestTask)

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

Expand Down Expand Up @@ -288,6 +377,10 @@ class ShadowPlugin : Plugin<Project> {
}

companion object {
const val locateApkanalyzerTaskName = "locateApkanalyzer"
private fun Project.locateApkanalyzerResultPath() =
File(rootProject.buildDir, "shadow/ApkanalyzerPath.txt")

private fun buildAgpCompat(project: Project): AGPCompat {
return AGPCompatImpl()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ import java.io.File
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
* @param outputDir 生成文件的输出目录
* @param packageName 生成类的包名
* @param packageForR 生成对R.java引用时需要的R文件的包名
*/
fun generatePluginManifest(
xmlFile: File,
outputDir: File,
packageName: String,
packageForR: String
packageName: String
) {
val androidManifest = AndroidManifestReader().read(xmlFile)
val generator = PluginManifestGenerator(packageForR)
val generator = PluginManifestGenerator()
generator.generate(androidManifest, outputDir, packageName)
}
Loading

0 comments on commit a30a15c

Please sign in to comment.