【Gradle】AGP升级7.0及适配Replugin的多包名工程改造

前言

随着AGP版本以及kotlin的普及,以及compose逐渐被大家接受,为了可以使用更多的新特性以及更好的性能,升级Android Gradle Plugin(AGP)以及Kotlin Gradle Plugin (KGP)已经是必不可少的步骤之一,其中AGP版本7.0、8.0的更新有较大的变化,本文会对自己的玩安卓项目进行AGP7.0的升级并且分享最近遇到的多包名工程化改造以及适配遇到的问题

AGP 版本参考与升级参考

推荐参考AGP 7.0升级

Gradle插件版本与gradle版本

AGP插件版本 所需的最低 Gradle 版本
8.1 8.0
8.0 8.0
7.4 7.5
7.3 7.4
7.2 7.3.3
7.1 7.2
7.0 7.0
4.2.0+ 6.7.1
4.1.x 6.5
4.0.x 6.1.1
3.6.x 5.6.4
3.5.x 5.4.1
3.4.x 5.1.1
3.3.x 4.10.x
3.2.x 4.6.x
3.1.x 4.4.x
3.0.x 4.1.x

Gradle 版本与kotlin插件版本

KGP version Gradle min and max versions AGP min and max versions
1.9.0 6.8.3 -- 7.6.0 4.2.2 -- 7.4.0
1.8.20 6.8.3 -- 7.6.0 4.1.3 -- 7.4.0
1.8.0 6.8.3 -- 7.3.3 4.1.3 -- 7.2.1
1.7.20 6.7.1 -- 7.1.1 3.6.4 -- 7.0.4
1.7.0 6.7.1 -- 7.0.2 3.4.3 -- 7.0.2
1.6.20 6.1.1 - 7.0.2 3.4.3 - 7.0.2

Gradle版本、Kotlin语言版本参考

Gradle version Embedded Kotlin version Kotlin Language version
5.0 1.3.10 1.3
5.1 1.3.11 1.3
5.2 1.3.20 1.3
5.3 1.3.21 1.3
5.5 1.3.31 1.3
5.6 1.3.41 1.3
6.0 1.3.50 1.3
6.1 1.3.61 1.3
6.3 1.3.70 1.3
6.4 1.3.71 1.3
6.5 1.3.72 1.3
6.8 1.4.20 1.3
7.0 1.4.31 1.4
7.2 1.5.21 1.4
7.3 1.5.31 1.4
7.5 1.6.21 1.4
7.6 1.7.10 1.4
8.0 1.8.10 1.8
8.2 1.8.20 1.8
8.3 1.9.0 1.8

AndroidStudio版本与kotlin插件版本

Android Studio 版本 Kotlin 插件版本 Kotlin 版本
3.0.x 1.2.x 1.2.x
3.1.x 1.2.x - 1.2.61 1.2.x
3.2.x 1.3.x - 1.3.50 1.3.x
3.3.x 1.3.x - 1.3.50 1.3.x
3.4.x 1.3.x - 1.3.50 1.3.x
3.5.x 1.3.x - 1.3.50 1.3.x
3.6.x 1.3.x - 1.3.72 1.3.x
4.0.x 1.4.x - 1.4.31 1.4.x
4.1.x 1.4.x - 1.4.31 1.4.x
4.2.x 1.5.x - 1.5.21 1.5.x
4.3.x 1.5.x - 1.5.21 1.5.x
2020.1.x 1.4.x - 1.4.31 1.4.x
2020.2.x 1.4.x - 1.4.31 1.4.x
2020.3.x 1.4.x - 1.4.31 1.4.x
4.4.x 1.6.x 1.6.x

Android Studio 与 AGP版本

AGP 7.0 升级

升级与三方库兼容

接下来进入正式的升级,如果用的是最新的AS,可以发现已经有了推荐升级的提示并且可以一键升级

点击后会自动修改gradle以及agp版本,但是还是有一些需要修改,以下是几个修改点

  • gradle/wrapper/gradle-wrapper.properties 设置镜像提高下载速度 https\://mirrors.cloud.tencent.com/gradle/gradle-7.5-all.zip
  • 设置的gradle#addBuildListener 监听器 buildStarted(gradle: Gradle) 方法被移除了
  • com.android.library 模块不再需要设置版本号和版本名称,其他的直接按照提示升级即可
  • kotlin版本升级至1.5.x版本

升级到7.0 也遇到一些问题,比如常见的一些三方库三方插件的升级以及兼容

  • 玩安卓项目接入booster,这里直接更新到最新版本
  • ARouter 不兼容,这里推荐直接替换为 therouter官网有一键升级的工具,这里也不展开
  • 兼容老换肤框架,因为换肤使用的换肤框架也很久没更新了,这里为了快速解决直接强制指定appcompat库版本
properties 复制代码
//    指定androidx版本,防止因为换肤库导致的崩溃
//    https://blog.csdn.net/charlinopen/article/details/126625175
    configurations.all {
        resolutionStrategy {
            force("androidx.appcompat:appcompat:1.2.0")
        }
    }

gradle插件等兼容

  • gradle插件提示重复注册

这里有点莫名其妙,因为本身的玩安卓项目带buildSrc有一些自己练手的transform,这里根据提示去指定一个重复的策略

groovy 复制代码
// https://docs.gradle.org/7.5/javadoc/org/gradle/api/file/DuplicatesStrategy.html
tasks.findByName("processResources").configure {
    duplicatesStrategy 'include'
}
  • buildSrc内一些工具类找不到了,这里应该是新的gradle不附带这些三方库,直接根据提示添加对应的依赖库,我这里org.apache.commons.io 下面的一些工具类
groovy 复制代码
dependencies {
    ...
    implementation("commons-io:commons-io:2.4")
    implementation("commons-codec:commons-codec:1.10")

    ...
}

至此整个工程也顺利跑起来了,插件也顺利运行,当然不同的项目引入不同的框架可能还会有不同的问题需要解决,这个就需要根据项目去具体看了,这里没有对替换tramsform的api进行适配,后续有需要的话可以参考虾佬的文章~

多包名工程改造

这里的项目背景是需要将原有的工程进行包名修改,并且是需要兼容多个包名同时存在,即单个工程可以根据需要输出多个不同包名的apk,同时在不同的国家在一些业务逻辑上有一定的区分 因为项目之前也做了基础组件和业务组件的一些拆分,这里采用的改造方式是 基础组件拓展(去除写死逻辑改为)+ 业务组件使用多favors与dimens + app壳工程区分各类代码与资源的差异,再加上一些本地/云端配置进行兼容

抽离App壳工程

如果已经抽离可以忽略此步骤,因为app模块下R文件与BuildConfig文件是根据applicationId去生成,如果修改包名会导致变更,从而使app目录下有用到资源与BuildConfig的地方需要修改,当然也可以指定一个namespace固定生成,这里是拆分了一个隔离层,将相关的资源以及代码逻辑"下层"

壳工程修改

  • 资料参考
  1. 官网 -- 配置build 变种
  2. 官网 -- 配置多变种
  • 工程配置

主要包括多favor的设置以及包名的动态化传参,这里有个坑就是看官网是可以根据favors改变applicationId,但是实际上这里虽然改变了包名,但是对应生成的BuildConfig文件是不会跟着变的,这样的话可能会影响到一些插件的流程(比如replugin)

kotlin 复制代码
android {

    ...

    defaultConfig {
        applicationId = rootProject.ext["applicatonId"].toString()
            ...
    }

    //   add single flavor
    //    flavorDimensions += "nation"
    flavorDimensions += listOf("nation", "product")
    productFlavors{

        create("productA"){
            dimension = "product"
        }

        create("productB"){
            dimension = "product"
        }

        create("nationA"){
            dimension = "nation"
        }

        create("nationB"){
            dimension = "nation"
        }

    }

}

之后便可以根据不同的变种去定义不同的资源目录,其优先级可以参考上面的官网,以下引用官网

... 您可以让源代码集目录包含您希望只针对某些配置打包在一起的代码和资源。例如,如果您要构建"demoDebug"build 变体("demo"产品变种和"debug"build 类型的混合产物),Gradle 会查看这些目录,并为它们指定以下优先级:

  1. src/demoDebug/(build 变体源代码集)
  2. src/debug/(build 类型源代码集)
  3. src/demo/(产品变种源代码集)
  4. src/main/(主源代码集) ... 如果您将多个产品变种组合在一起,那么这些产品变种的优先级由它们所属的变种维度决定。使用 android.flavorDimensions 属性列出变种维度时,属于您列出的第一个变种维度的产品变种的优先级高于属于第二个变种维度的产品变种,依此类推。此外,您为产品变种组合创建的源代码集的优先级高于属于各个产品变种的源代码集。

跨层级依赖问题

上层定义了多个变种后,其实只是针对当前module,如果依赖的其他模块有区分的需求,只要定义相同的变种名称即可,gradle会对变种类型进行传递,这里可以将定义变种的代码进行文件抽取减少重复工作,但是这里又有一个问题是这个变种类型传递不能进行跨模块传递 简单来说就是A 依赖 B,B依赖了C,A和C都定义了多个变种,但是B没定义,会编译失败:B模块并不知道要依赖哪个变种的C模块

groovy 复制代码
Could not determine the dependencies of task ':upper_module:compileDebugRenderscript'.
    > Could not resolve all task dependencies for configuration ':upper_module:debugCompileClasspath'.
        > Could not resolve project :base_module.
    Required by:
    project :upper_module
> The consumer was configured to find an API of a component, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'. However we cannot choose between the following variants of project :base_module:
    - flavor_twoDebugApiElements
- flavor_oneDebugApiElements
All of them match the consumer attributes:
    - Variant 'flavor_twoDebugApiElements' capability NextApp:base_module:unspecified declares an API of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug':
- Unmatched attributes:
    - Provides attribute 'com.android.build.gradle.internal.attributes.VariantAttr' with value 'flavor_twoDebug' but the consumer didn't ask for it
- Provides attribute 'default' with value 'flavor_two' but the consumer didn't ask for it
- Doesn't say anything about its target Java environment (preferred optimized for Android)
- Variant 'flavor_oneDebugApiElements' capability NextApp:base_module:unspecified declares an API of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug':
- Unmatched attributes:
    - Provides attribute 'com.android.build.gradle.internal.attributes.VariantAttr' with value 'flavor_oneDebug' but the consumer didn't ask for it
- Provides attribute 'default' with value 'flavor_one' but the consumer didn't ask for it
- Doesn't say anything about its target Java environment (preferred optimized for Android)
The following variants were also considered but didn't match the requested attributes:
- Variant 'flavor_fourReleaseApiElements' capability NextApp:base_module:unspecified declares an API of a component:
    - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'
- Other compatible attribute:
    - Doesn't say anything about its target Java environment (preferred optimized for Android)
- Variant 'flavor_fourReleaseRuntimeElements' capability NextApp:base_module:unspecified declares a runtime of a component:
    - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'
- Other compatible attribute:
    - Doesn't say anything about its target Java environment (preferred optimized for Android)
- Variant 'flavor_threeReleaseApiElements' capability NextApp:base_module:unspecified declares an API of a component:
    - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'
- Other compatible attribute:
    - Doesn't say anything about its target Java environment (preferred optimized for Android)
- Variant 'flavor_threeReleaseRuntimeElements' capability NextApp:base_module:unspecified declares a runtime of a component:
    - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'
- Other compatible attribute:
    - Doesn't say anything about its target Java environment (preferred optimized for Android)

这里在stackoverflow找到了类似的问题,借用回答的示意图,问题链接

这里有几个办法

  1. 指定一个默认的匹配策略
  2. 给B模块定义一样的favors
  3. 重新进行架构拆分

因为这里遇到是基础组件存在差异,但是我认为基础组件应该与业务无关,不应该存在业务的favor拆分,所以对基础组件进行了改造,将与业务相关的东西拆到了上层,以注册/初始化的形式由最上层进行传递

适配谷歌/华为 插件

这里主要是有个google-services.json 文件,华为的也是类似,多变种需要根据不同的包名去放不同的文件,这里直接放在对应变种的文件夹即可,如果找不到的话可以查看报错的提示,上面会有插件查找的路径,找一个合适的路径配置即可

适配Replugin插件

前面一切搞定后,假如工程使用了replugin插件,会发现有的apk,会出现 卡死/崩溃/不断重启 的现象,查看日志大概可以看到是找不到某个类,而且这个xxx是以包名开头的。

groovy 复制代码
2023-12-06 17:42:17.017 26546-26546 RePlugin.ws001           D  plugin process checker: default, index=0
2023-12-06 17:42:17.038 26546-26546 RePlugin.ws001           D  PACM: restore table: size=0
2023-12-06 17:42:17.041 26546-26546 RePlugin.ws001           D  list plugins from persistent process
2023-12-06 17:42:17.047 26546-26546 ActivityThread           E  Failed to find provider info for xxx.loader.p.main
2023-12-06 17:42:17.047 26546-26546 RePlugin.ws001           D  proxy fetch binder: cursor is null
2023-12-06 17:42:17.047 26546-26546 RePlugin.ws001           D  host binder = null
2023-12-06 17:42:17.047 26546-26546 ws001                    E  p.p fhb fail

这里我们知道replugin会通过gradle插件去生成大量的"坑位",其中也会在manifest定义,但是按理说这个应该是跟随包名的,怎么会导致包名不匹配呢? 这里我们直接通过这个报错,搜replugin宿舍插件的源码,可以看到这里的形参确实是applicationID

groovy 复制代码
/**
     * 动态生成插件化框架中需要的组件
     *
     * @param applicationID 宿主的 applicationID
     * @param config 用户配置
     * @return String       插件化框架中需要的组件
     */
    def static generateComponent(def applicationID, def config) {

        def writer = new StringWriter()
        def xml = new MarkupBuilder(writer)

        /* UI 进程 */
        xml.application {

            /* 需要编译期动态修改进程名的组件*/

            String pluginMgrProcessName = config.persistentEnable ? config.persistentName : applicationID

            // 常驻进程Provider
            provider(
                    "${name}":"com.qihoo360.replugin.component.process.ProcessPitProviderPersist",
                    "${authorities}":"${applicationID}.loader.p.main",
                    "${exp}":"false",
                    "${process}":"${pluginMgrProcessName}")

        // 将单进程和多进程的组件相加
        normalStr + generateMultiProcessComponent(applicationID, config)
    }

再看调用的地方

groovy 复制代码
...
def generateBuildConfigTask = VariantCompat.getGenerateBuildConfigTask(variant)
                def appID = generateBuildConfigTask.appPackageName
                def newManifest = ComponentsGenerator.generateComponent(appID, config)
...

发现这个值是从generateBuildConfigTask取的,一开始还没反应过来,后来发现其实这个task就是生成BuildConfig的task,这也是一开始提到namespaceBuildConfig文件的原因,最后经过尝试和验证,决定将 壳工程的 AndroidManife.xml 文件中的 package 标识去掉,applicationId 通过构建时动态传入解决 或者也可以修改replugin插桩的逻辑,但是因为对框架本身不是很熟,最后还是选择了比较保守的方案~

弊端与思考

多favor方案虽然可以适配所有的资源并且也是官方的API,但是实际操作起来还是会有很多问题

  • 面对越来越多的国家,对应的favor会成倍增长
  • 如何做好"防呆"控制,对多favor方案不熟悉的同学可能会漏提交一些代码/资源文件
相关推荐
Estar.Lee4 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh5 小时前
uiautomator案例
android
工业甲酰苯胺6 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3436 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee7 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯8 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey9 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!11 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟12 小时前
Android音频采集
android·音视频
小白也想学C13 小时前
Android 功耗分析(底层篇)
android·功耗