地点:深圳、西丽湖环湖碧道
时间: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:Nestedval parameters: Property<ParametersT>//一些 asm 参数@get:Nestedval 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:Inputval 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 trueurl 'http://developer.huawei.com/repo/'
}
AGP8+
maven {isAllowInsecureProtocol = trueurl = 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.versionif (!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 DefaultDependencyResolveDetailsval module =details.requested.module.group + ":" + details.requested.module.name + ":" + details.requested.version//如果存在多个变体,会执行多次,可以去重if (!dp.contains(module)) {dp.add(module)}callback.onResult(dp)}}}
}