适配 AGP8.5 版本,各种问题(三)
地点:深圳、西丽湖环湖碧道
时间:2025年2月23日
1. 签名创建 & 配置
应该说是 kts
和 gradle
的区别
build.gradle.kts
signingConfigs {
create("myrelease") {
storeFile = File(project.projectDir, "aa.keystore")
storePassword = "aa"
keyAlias = "aa"
keyPassword = "aa"
}
create("mydebug") {
storeFile = File(project.projectDir, "bb.keystore")
storePassword = "bb"
keyAlias = "bb"
keyPassword = "bb"
}
}
buildTypes {
debug {
signingConfig = signingConfigs["mydebug"]
}
release {
signingConfig = signingConfigs["myrelease"]
}
}
}
build.gradle
signingConfigs {
debug {
keyAlias 'aa'
keyPassword 'aa'
storeFile file('aa.keystore')
storePassword 'aa'
}
release {
keyAlias 'bb'
keyPassword 'bb'
storeFile file('bb.keystore')
storePassword 'bb'
}
}
buildTypes {
debug {
signingConfig signingConfigs.release
}
release {
signingConfig signingConfigs.release
}
}
}
2. Groovy 编译问题
问题: Groovy 代码写的类找不到,编译失败
我的插件里存在 Kotlin、Java、Groovy 代码(历史问题),升级到 AGP8+ 之后编译失败,在 AGP7+ 时没有发现问题,可能是那时候还没引入 Kotlin 代码。
我们看打包成功的例子,Kotlin、Java、Groovy
代码编译任务执行先后顺序是这样的:
- compliteKotlin
- compliteJava
- compliteGroovy
我遇到的问题是:项目里面存在 Groovy 代码(位于 groovy 目录下),Kotlin 和 Java 代码(位于 java 目录下),当编译项目是发生了错误,Java 和 Kotlin 代码里找不到 Groovy 代码的类
看代码编译任务顺序可能理解了大概:Groovy 代码还没编译呢,就先编译引用了 Groovy 的 Java 代码肯定报错吧
解决:
- 能不能把 Groovy 编译任务先于前面两个?(我不知道如何处理,未验证,只是一个猜想)
- 把 Groovy 代码以 Kotlin 重写(是可以的,代码太多只是重写麻烦)
3. compliteGroovy 未执行
问题: Groovy 代码为打包进 jar 里
在插件发布时,发现项目里 java
目录下的 Groovy 代码没有生成到 jar 里面
再次打包也注意到运行的 Task 列表里面没有 compliteGroovy
,说 Groovy 代码根本没有参与编译过程
后来关注到 main 下面可以创建者四个目录:
- groovy:存放 Groovy 代码
- java:存放 Java 代码
- kotlin:存放 Kotlin 代码
- resources:存放资源文件
解决:把 Groovy 代码移到 groovy 目录下重新编译,终于看到了 compliteGroovy 也编译正常
在 AGP7+ 时我的 Groovy 代码也是在 java
目录下却能正确编译,到 AGP8+ 就不行了
4. AsmClassVisitorFactory 编译失败
错误日志:Could not serialize value of type GameManagerClassVisitor
注意:他是运行时编译失败(就是引入插件后,在 app 目录打包 assembleRelease 执行时),不是插件发布编译成 jar 时
我的代码:
我有一个常量gameManagerClassname
,根据以往的编码习惯,直接接声明为对象的属性(感觉没问题啊),可以一开始编译就报错!(超乎想象,全是知识盲区)
abstract class GameManagerClassVisitor : AsmClassVisitorFactory<EmptyParam> {
private val gameManagerClassname = "com.vimedia.game.GameManager"
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
return FindDispatchMethodVisitor(Opcodes.ASM9, nextClassVisitor)
}
override fun isInstrumentable(classData: ClassData): Boolean {
return classData.className == gameManagerClassname
}
}
根据前两篇文章,大概了解到 AsmClassVisitorFactory 的是一个接口:
interface AsmClassVisitorFactory<ParametersT : InstrumentationParameters> : Serializable {
//这些参数可被实例化,并在实例化对象时注入
@get:Nested
val parameters: Property<ParametersT>
//一些 asm 参数
@get:Nested
val instrumentationContext: InstrumentationContext
// asm 工厂类,实际上委托给别的具体类实现操作
fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor
//返回布尔值,执行当前类是否需要处理
fun isInstrumentable(classData: ClassData): Boolean
}
解决:所以我把这个 gameManagerClassname 转换为 InstrumentationParameters
实现,问题解决了
abstract class GameManagerClassVisitor : AsmClassVisitorFactory<GameManagerClassVisitor.P> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
return FindDispatchMethodVisitor(Opcodes.ASM9, nextClassVisitor)
}
interface P : InstrumentationParameters {
@get:Input
val gameManagerClassname: Property<String>
}
}
如何传递参数:
private fun registerTransform(project: Project) {
project.plugins.withType(AppPlugin::class.java) {
val androidExt =
project.extensions.getByType(AndroidComponentsExtension::class.java)
androidExt.onVariants { variants ->
variants.instrumentation.transformClassesWith(
GameManagerClassVisitor::class.java,
InstrumentationScope.ALL
) { params ->
params.gameManagerClassname.set("com.vimedia.game.GameManager")
}
}
}
}
5. 添加 Http Maven 仓库
AGP7+
maven {
allowInsecureProtocol true
url 'http://developer.huawei.com/repo/'
}
AGP8+
maven {
isAllowInsecureProtocol = true
url = uri("http://developer.huawei.com/repo/")
}
6. 添加 Manifest 占位符
AGP7+
//先获取 Android 扩展
if (isAppExtension(mProject)) {
android = mProject.extensions.findByName("android")
} else {
android = null
}
//placeInMap 就是一个 Map<String, Object>
android.defaultConfig.manifestPlaceholders = placeInMap
AGP8+
val manifestPlaceholder = mapOf(
"PRJID" to "333",
"CHANNEL" to "222",
"APPID" to "111",
"APPKEY" to "123"
)
private fun setManifestPlaceholders(
project: Project,
manifestPlaceholder: Map<String, String>
) {
project.plugins.withType(AppPlugin::class.java) {
val appAndroidExt =
project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
appAndroidExt.onVariants { variant ->
//如果存在多个变体,会执行多次,可根据需要调整
manifestPlaceholder.forEach { (k, v) ->
CmdColorPrintUtils.outputGreen("添加 Manifest 参数:$k -> $v")
variant.manifestPlaceholders.put(k, v)
}
}
}
}
7. 获取所有依赖项
AGP7+
static void loadAddDependencies(Project project, DependenciesCallback callback) {
def dp = new ArrayList()
project.configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
String module = details.requested.module.group + ":" + details.requested.module.name + ":" + details.requested.version
if (!dp.contains(module)) {
dp.add(module)
}
callback.onResult(dp)
}
}
}
AGP8+
@JvmStatic
fun loadAddDependencies(project: Project, callback: DependenciesCallback) {
val dp = ArrayList<String>()
val androidExt =
project.extensions.getByType(AndroidComponentsExtension::class.java)
androidExt.onVariants { variants ->
variants.components.forEach { component ->
component.runtimeConfiguration.resolutionStrategy.eachDependency {
val details = it as DefaultDependencyResolveDetails
val module =
details.requested.module.group + ":" + details.requested.module.name + ":" + details.requested.version
//如果存在多个变体,会执行多次,可以去重
if (!dp.contains(module)) {
dp.add(module)
}
callback.onResult(dp)
}
}
}
}