文章

03.AGP升级

03.AGP升级

为什么要选择 VersionCatalog 来做依赖管理?AGP 升级到 3.x

官网文档:
https://developer.android.com/studio/releases/gradle-plugin.html#revisions
升级到 AGP3.0.0+
https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html
新特性:

  1. Java8 支持
  2. 新的依赖匹配机制
  3. aapt2

1、升级 android gradle plugin(AGP) 和 gradle wrapper 到最新版

  • 升级 android gradle plugin(AGP) 到 3.0.0 及以上

classpath ‘com.android.tools.build:gradle:3.0.1’

  • 升级 gradle wrapper 到 4.1 及以上
1
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
  • 添加 google() 仓库
1
2
3
4
repositories{
    google()
    // jcenter()、maven()
}

2、新的依赖配置

依赖关键字的改变

  1. api:对应之前的 compile 关键字,功能一模一样。会传递依赖,导致 gradle 编译的时候遍历整颗依赖树
  2. implementation:对应之前的 compile,与 api 类似,关键区别是不会有依赖传递
  3. compileOnly:对应之前的 provided,依赖仅用于编译期不会打包进最终的 apk 中
  4. runtimeOnly:对应之前的 apk,与上面的 compileOnly 相反,作用于运行时,编译期没有

关于 implementation 与 api 的区别,主要在依赖是否会传递上。如:A 依赖 B,B 依赖 C,若使用 api 则 A 可以引用 C,而 implementation 则不能引用

这里更推荐用 implementation,一是不会间接的暴露引用,清晰知道目前项目的依赖情况;二是可以提高编译时依赖树的查找速度,进而提升编译速度。详见 SO 的这个回答,讲得非常详细了:【stackoverflow】gradle-implementation-vs-api-configuration

新的配置旧的配置行为表现
implementationcompile你的 module 配置了 implementation 依赖,它让 gradle 知道这个 module 不想让其他的 module 依赖泄露了,也就是说,这个依赖只能在直接依赖可用,间接依赖不可用。这种可以有效减少编译时间,一般 app
test
都应该配置这种。不能依赖传递
apicompile行为和之前的 compile 一样,可以进行依赖传递; 只在 library module 中使用;过多的 api 依赖会增大编译时间
compileOnlyprovided只在编译时添加依赖,不会添加到输出 apk 包
runtimeOnlyapk只在 apk 输出时添加依赖。在编译时不会添加

出现这种情况: 将 library 的 implementation 改成 api 即可
Error:Execution failed for task ‘:app :preDebugBuild’.
Android dependency ‘com.android.support:support-v4’ has different version for the compile (23.1.1) and runtime (27.1.0) classpath. You should manually set the same version via DependencyResolution

3、android annotation processor config

  1. 移除 android-apt 相关的 plugin,如:
1
classpath 'com.neenbedankt.gradle.plugins:android-apt :1.8'
  1. apt 采用 annotationProcessor 替代
1
annotationProcessor 'com.android.databinding:compiler :3.0.0'
  1. 如果有用到类似 Realm 这种第三方的 plugin,确保升级到最新版试试(旧版的 Realm 用的还是 android-apt),突然发现升级到最新版后 api 接口被改了
1
2
3
4
5
6
7
8
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin :4.2.0"
    }
}
  1. Use the annotation processor dependency configuration
    在之前的 agp 插件版本,在 compile 的依赖路径中,compile classpath 自动地添加到 processor classpath 中。也就是说,你添加了一个 annotation processor 到 compile classpath 中。这就影响了性能,添加了大量的不必要的依赖到 processor。
    当用 AGP3.x 时,你必须添加 annotation processors 到 processor classpath 当用 annotationProcessor 依赖配置时:
1
2
3
4
dependencies {
    // ...
    annotationProcessor 'com.google.dagger:dagger-compiler :<version-number>'
}

关闭 annotation processor 错误检测,这个特性不久就来会关闭

1
2
3
4
5
6
7
8
9
10
11
android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath false
            }
        }
    }
}

如,tinker 报错,设置 includeCompileClasspath true 即可

1
2
3
4
5
Error:Execution failed for task ':app :javaPreCompileDebug'.
> Annotation processors must be explicitly declared now.  The following dependencies on the compile classpath are found to contain annotation processor.  Please add them to the annotationProcessor configuration.
    - tinker-android-anno-1.7.11.jar (com.tencent.tinker:tinker-android-anno :1.7.11)
  Alternatively, set android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true to continue with previous behavior.  Note that this option is deprecated and will be removed in the future.
  See https://developer.android.com/r/tools/annotation-processor-error-message.html for more details.

4、生成 APK 文件名属性 outputFile 变为只读

1
2
3
4
5
6
7
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def file = output.outputFile
        def apkName = 'xxx-xxx-xxx-signed.apk'
        output.outputFile = new File(file.parent, apkName)
    }
}

报错:

1
2
Error:(233, 0) Cannot set the value of read-only property 'outputFile' for ApkVariantOutputImpl_Decorated{apkData=Main{type=MAIN, fullName=devDebug, filters=[]}} of type com.android.build.gradle.internal.api.ApkVariantOutputImpl.
< a href=" ">Open File</ a>

由于 outputFile 属性变为只读,需要进行如下修改,直接对 outputFileName 属性赋值即可:

1
2
3
4
5
6
applicationVariants.all { variant ->
    variant.outputs.all {
        def apkName = 'xxx-xxx-xxx-signed.apk'
        outputFileName = apkName
    }
}

5、flavor

1) 渠道需要声明 flavor dimensions

报错:

1
Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html

也就是也是 flavor 渠道都必须归属一个 dimension,若只有一个 dimension,渠道中可以不写 dimension 属性,默认分配到该维度。直接添加一个默认的维度即可:如 flavorDimensions "dimension",当然 flavorDimensions 也可以设置多个维度,详见官方实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Specifies two flavor dimensions.
flavorDimensions "mode", "minApi"

productFlavors {
    free {
        // Assigns this product flavor to the "tier" flavor dimension. Specifying
        // this property is optional if you are using only one dimension.
        dimension "mode"
        ...
            }

    paid {
        dimension "mode"
        ...
            }

    minApi23 {
        dimension "minApi"
        ...
            }

    minApi18 {
        dimension "minApi"
        ...
            }
}

2) 库多 variant 依赖方式的修改 (variant=flavor*debug)

AGP3.x 引入了新的 variant 自动匹配机制,也就是说 app 的 flavorDebug variant 会自动匹配 library 的 flavorDebug variant.
回顾一下旧的方式,如果 app 在某个 variant 下需要依赖 library 相应的类型,需要按照下面的方式声明依赖:

1
2
3
4
5
6
dependencies {
    // This is the old method and no longer works for local
    // library modules:
    debugCompile project(path: ':library', configuration: 'debug')
    releaseCompile project(path: ':library', configuration: 'release')
}

新的方式,gradle 会自动感知并匹配对应的 variant(前提是 app 与 library 中有对应的 variant 类型):

1
2
3
4
5
6
7
dependencies {
    // Instead, simply use the following to take advantage of
    // variant-aware dependency resolution. You can learn more about
    // the 'implementation' configuration in the section about
    // new dependency configurations.
    implementation project(':library')
}

3) 处理 app 与 lib 的依赖匹配问题

上面我们了解到新的 variant 匹配机制,但若 app 或 library 中不存在对应的 variant 类型呢?匹配将如何进行?下面列出了可能出现的几种情形:

  1. app 有某个 build type 但 library 没有,可以通过 matchingFallbacks 属性来设置回退策略,提供可能的匹配列表,会选择第一个可用的,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// In the app's build.gradle file.
android {
    buildTypes {
        debug {}
        release {}
        staging {
            // Specifies a sorted list of fallback build types that the
            // plugin should try to use when a dependency does not include a
            // "staging" build type. You may specify as many fallbacks as you
            // like, and the plugin selects the first build type that's
            // available in the dependency.
            matchingFallbacks = ['debug', 'qa', 'release']
        }
    }
}

e5bit
若希望可以针对 app 的每个 build type 都执行相同的回退策略(例如我们大量的 library 只有一个 release 的 build type),则可以使用批量指令:

1
2
3
buildTypes.all { type ->
    type.matchingFallbacks = ['release']
}

注意:在该情景下,若 library 中有某个 build type 但 app 没有,不会对 app 有任何影响

  1. 在同一个 dimension 维度下,如:tier,若 app 中有某个 flavor 但 library 却没有
    同样通过 matchingFallbacks 属性来设置回退策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// In the app's build.gradle file.
android {
    defaultConfig{
    // Do not configure matchingFallbacks in the defaultConfig block.
    // Instead, you must specify fallbacks for a given product flavor in the
    // productFlavors block, as shown below.
    }
    flavorDimensions 'tier'
    productFlavors {
        paid {
            dimension 'tier'
            // Because the dependency already includes a "paid" flavor in its
            // "tier" dimension, you don't need to provide a list of fallbacks
            // for the "paid" flavor.
        }
        free {
            dimension 'tier'
            // Specifies a sorted list of fallback flavors that the plugin
            // should try to use when a dependency's matching dimension does
            // not include a "free" flavor. You may specify as many
            // fallbacks as you like, and the plugin selects the first flavor
            // that's available in the dependency's "tier" dimension.
            matchingFallbacks = ['demo', 'trial']
        }
    }
}

注意:在该情景下,若 library 中有某个 flavor 但 app 却木有,不会对 app 有任何影响

  1. library 中有某个 dimension 维度,但 app 中却没有
    可以通过 missingDimensionStrategy 属性来设置选择策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// In the app's build.gradle file.
android {
    defaultConfig{
    // Specifies a sorted list of flavors that the plugin should try to use from
    // a given dimension. The following tells the plugin that, when encountering
    // a dependency that includes a "minApi" dimension, it should select the
    // "minApi18" flavor. You can include additional flavor names to provide a
    // sorted list of fallbacks for the dimension.
    missingDimensionStrategy 'minApi', 'minApi18', 'minApi23'
    }
    flavorDimensions 'tier'
    productFlavors {
        free {
            dimension 'tier'
            // You can override the default selection at the product flavor
            // level by configuring another missingDimensionStrategy property
            // for the "minApi" dimension.
            missingDimensionStrategy 'minApi', 'minApi23', 'minApi18'
        }
        paid {}
    }
}

说明:其中 missingDimensionStrategy 属性的第一个值为 dimension 维度,后面的 Strings 为该维度下的渠道 flavors。

在该情景下,若 app 中有某个 dimension 维度,但 library 中却没有,不会对 app 有任何影响

  1. 若 library 没有任何 dimension 和 flavor,则不需 app 做任何 flavor 的回退处理~

其实诸如 dimension 的声明以及提供匹配回退策略都是为了实现精确的 variant 匹配

6、对 Java8 支持

AGP3.x,jack 被官方弃用了,用了最新的 desugar 方案替代。
禁用特性

1
android.enableDesugar=false

Java8 支持的特性:

  1. lambda 表达式
  2. Method References
  3. Type Annotations
  4. Default and static interface methods
  5. Repeating annotations

stream 及 function 包下的 api 只能在 api24+ 以上才可以使用

AGP7.0

VersionCatalog 版本管理

AGP7.0 脚本变更

[[03.Gradle 自定义插件#新版本Gradle配置更改]]

Enable configuration caching (截止 AGP7.x 还是实验阶段)

Gradle 生命周期分为 Initialization、Configuration 和 Execution Phase;Task Execution 已经有缓存了,但 Configuration 阶段还没有缓存,通过下面配置就可以启动 Configuration cache:

1
2
3
# configuration cache
org.gradle.unsafe.configuration-cache=true
org.gradle.unsafe.configuration-cache-problems=warn

需要 task 适配 Configuration Cache

适配 configuration-cache

Configuration cache

遇到的问题

某些库的 task 未适配 configuration-cache,会报错:

1
2
3
4
5
6
7
8
9
10
11
12
Configuration cache state could not be cached: field 'actions' from type 'org.gradle.api.DefaultTask': error writing value of type 'java.util.ArrayList'
> Configuration cache state could not be cached: field 'closure' from type 'org.gradle.api.internal.AbstractTask$ClosureTaskAction': error writing value of type 'com.chaquo.python.PythonPlugin$_createAssetsTasks_closure21$_closure46'
   > Configuration cache state could not be cached: field 'variant' from type 'com.chaquo.python.PythonPlugin$_createAssetsTasks_closure21$_closure46': error writing value of type 'groovy.lang.Reference'
      > Configuration cache state could not be cached: field 'value' from type 'groovy.lang.Reference': error writing value of type 'com.android.build.gradle.internal.api.ApplicationVariantImpl'
         > Configuration cache state could not be cached: field 'testVariant' from type 'com.android.build.gradle.internal.api.ApplicationVariantImpl': error writing value of type 'com.android.build.gradle.internal.api.TestVariantImpl'
            > Configuration cache state could not be cached: field 'variantData' from type 'com.android.build.gradle.internal.api.TestVariantImpl': error writing value of type 'com.android.build.gradle.internal.variant.TestVariantData'
               > Configuration cache state could not be cached: field 'testedVariantData' from type 'com.android.build.gradle.internal.variant.TestVariantData': error writing value of type 'com.android.build.gradle.internal.variant.ApplicationVariantData'
                  > Configuration cache state could not be cached: field 'artifacts' from type 'com.android.build.gradle.internal.variant.ApplicationVariantData': error writing value of type 'com.android.build.api.artifact.impl.ArtifactsImpl'
                     > Configuration cache state could not be cached: field 'storageProvider' from type 'com.android.build.api.artifact.impl.ArtifactsImpl': error writing value of type 'com.android.build.api.artifact.impl.StorageProviderImpl'
                        > Configuration cache state could not be cached: field 'fileStorage' from type 'com.android.build.api.artifact.impl.StorageProviderImpl': error writing value of type 'com.android.build.api.artifact.impl.TypedStorageProvider'
                           > Configuration cache state could not be cached: field 'singleStorage' from type 'com.android.build.api.artifact.impl.TypedStorageProvider': error writing value of type 'java.util.LinkedHashMap'
                              > java.util.ConcurrentModificationException (no error message)

This occurred due to the Cache of Build Gradle In the cache, some Libraries are not Cached so it Gives the Error
Fixed:

1
2
// gradle.properties
org.gradle.unsafe.configuration-cache=false

Transform 7.2 的废弃

[[02.AGP之Transform#7.2 GAP Transform适配]]

pluginManagement 适配

[[03.Gradle 自定义插件#Plugin Management]]

AGP8.0

AGP8.0 变化

  1. 要有 namespace
  2. buildFeature 需要增加 buildConfig true 才会生成 BuildConfig

R class field 不是常量了

  • 不是常量,对编译速度有提升,更好的资源 shrink
  • switch case 需要替换成 if else 或 when
  • AGP8.0 及以上默认行为

w6sp8

  • 保持旧的行为,在 gradle.properties:
1
android.nonFinalResIds=false

其他

Migrate from buildscript to plugins block

本文由作者按照 CC BY 4.0 进行授权