Android 工具命令——Gradle配置问题

本文链接:https://rainmonth.github.io/posts/A190917.html

Android Gradle配置问题

实际项目开发过程中,可能遇到以下问题:

  1. 应用存在多套环境(测试、运维、正式);
  2. 应用需要在多个渠道上发布;
  3. 不同环境的apk可以安装在同一个手机上(方便测试同学测试);
  4. 不同环境的apk可以拥有不同的应用名称(方便测试同学测试);
  5. 不同渠道的apk可以拥有不同的资源文件(如某些渠道的启动页不同);
  6. 不同环境引用不同的常量;

Gradle中签名文件的处理

签名文件涉及到应用的隐私,不能直接采用共有的仓库进行管理,可以采用配置文件的形式来处理

  • 在项目根目录下新建一个文件:signing.properties(文件名称随意)

  • 打开signing.properties文件,添加如下内容

    1
    2
    3
    4
    5
    6
    7
    8
    # 签名文件路径
    KEY_STORE_PATH=./demo.keystore
    # 密码
    KEY_STORE_PASSWORD=demo
    # Alias
    KEY_STROE_ALIAS=demo.keystore
    # key 密码
    KEY_PASSWORD=demo

    注意:上面假设项目根目录下存在一个demo.keystore的文件,且其他信息如上所示

  • 打开项目的主module(一般为app module),在其build.gradle文件的添加如下内容(注意:添加位置:在android节点外

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 定义加载签名信息的方法
    def loadSignProperties() {
    File propFile = file('../signing.properties')
    if (propFile.exists()) {
    Properties props = new Properties()
    props.load(new FileInputStream(propFile))
    if (props.containsKey('KEY_STORE_PATH') && props.containsKey('KEY_STORE_PASSWORD') &&
    props.containsKey('KEY_STROE_ALIAS') && props.containsKey('KEY_PASSWORD')) {
    println 'signing.properties exist, and all props is here'
    android.signingConfigs.release.storeFile = file(props['KEY_STORE_PATH'])
    android.signingConfigs.release.storePassword = props['KEY_STORE_PASSWORD']
    android.signingConfigs.release.keyAlias = props['KEY_STROE_ALIAS']
    android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
    } else {
    println 'signing.properties exist, but some props is missing'
    android.buildTypes.release.signingConfig = null
    }
    } else {
    println 'signing.properties not exist'
    android.buildTypes.release.signingConfig = null
    }
    }
  • 调用上面方法加载签名信息

    1
    2
    # 注意该方法调用的实际
    loadSignProperties()

    注意:

    1. loadSignProperties()这个方法的调用必须在Android节点之后,因为方法里面使用了Android节点定义的属性,不然会报如下错误:Could not get unknown property 'release' for SigningConfig container
    2. File propFile = file('../signing.properties')这里制定签名配置文件的路径时,由于配置文件和当前文件不在同一个目录下,先要采用’..’ 回到根目录,在利用’/‘在当前文件中查找该配置文件

使用buildConfigField来定义常量的问题

在主module(一般只appmodule)的buildTypes节点中,可以在不同的编译类型如debug、release中制定buildConfigField的不同值,然后在编译后生成的BuildConfig来使用这个值,这给针对不同环境采用不同配置提供了可能,如debug中开启日志,release中关闭日志等,十分便利,但利用BuildConfig一定要充分保证其安全性。参考如下文章:

Android中使用BuildConfig.DEBUG必须知道的内幕

上述文章的结论:主module对library module的依赖都是release依赖,所以,如果你在library module中也采用了BuildConfig的话,他的值就一直对应的时release里面定义的那个值

解决方案

  1. 在library module的build.gradle中添加如下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    android {
    ......
    ......

    productFlavors {
    create('all') {

    }
    }
    publishNonDefault true
    }
    configurations {
    allDebug
    allRelease
    }

    文章中提到添加上述代码,但是添加后发现会报错,实际上只需要添加如下代码即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    android {
    ......
    ......

    productFlavors {
    }
    publishNonDefault true
    }
    configurations {
    allDebug
    allRelease
    }
  2. library module中的buildType要和主module中一致

  3. 改变主module对library的依赖方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:24.2.1'

    //依赖library
    debugCompile project(path: ':library', configuration: 'allDebug')
    releaseCompile project(path: ':library', configuration: 'allRelease')

    //省略其余依赖
    .....
    .....
    }

    其实,还要加上如下依赖:

    1
    compile project(path: ':library')

    不然你会发现在使用library的地方都会引用不到library中相关的类

  4. 解决上面的问题后,就放心大胆的添加buildConfigField了

gradle 3.0重命名生成的apk文件和改变输出路径

  • 重命名文件

    3.0之前的做法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    applicationVariants.all { variant ->
    if (variant.buildType.name == "release") {
    variant.outputs.all { output ->
    def outputFile = output.outputFile
    if (outputFile != null && outputFile.name.endsWith('.apk')) {
    // apk_渠道名-版本号-版本名称-编译时间.apk
    def fileName = "demo-${defaultConfig.versionCode}- v${defaultConfig.versionName}-${releaseTime()}.apk"
    output.outputFile = new File(outputFile.getParent(),fileName)
    //
    }
    }
    }
    }

    3.0之后的做法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    applicationVariants.all { variant ->
    if (variant.buildType.name == "release") {
    variant.outputs.all { output ->
    def outputFile = output.outputFile
    if (outputFile != null && outputFile.name.endsWith('.apk')) {
    // apk_渠道名-版本号-版本名称-编译时间.apk
    def fileName = "demo-${defaultConfig.versionCode}- v${defaultConfig.versionName}-${releaseTime()}.apk"
    output.outputFileName = fileName
    //
    }
    }
    }
    }
  • 更改输出路径

    输出路径如果不更改,就是在主module的build文件夹的apk目录下。

    3.0之前做法:

    直接在重命名的时候指定其输出路径,如:

    output.outputFile = new File(outputFile.getParent(),fileName)改为:

    output.outputFile = new File(distinationDir,fileName)

    3.0之后做法:(参考文章:3.0之后修改apk输出路径

    由于output.outputFile 属性变为只读,需采用如下方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    applicationVariants.all { variant ->
    //这个修改输出的APK路径
    variant.getPackageApplication().outputDirectory = new File(project.rootDir.absolutePath + "/apk")
    // 修改文件名方法一:
    variant.outputs.all { output->
    def outputFile = output.outputFile
    if(outputFile != null && outputFile.name.endsWith('.apk')) {
    def fileName = "demo-${defaultConfig.versionCode}- v${defaultConfig.versionName}-${releaseTime()}.apk"
    output.outputFileName = fileName
    }

    }

    //修改文件名方法二:
    variant.getPackageApplication().outputScope.apkDatas.forEach { apkData ->
    apkData.outputFileName = "AppName-" +
    variant.versionName + "_" +
    apk_time + "_" +
    variant.flavorName + "_" +
    variant.buildType.name + "_" +
    variant.signingConfig.name +
    ".apk"
    }
    }

    注意:这里修改输出文件的名字时,如果在每个buildType中单独设置,那么编译出的文件名就是最后定义的buildType中修改的名称,所以我在3.0之后修改apk名的时候,实在Android节点添加上述代码,然后再根据buildType.name 来对不同的buildType做区分处理。

带着问题去学习?

如何在Android Studio中查看某个gradle版本(如gradle:3.4.3)源码?

主项目下面build.gradle文件dependencies下的gradle插件版本声明文件,复制到主modulebuild.gradle文件的 dependencies下,并将前面的classpath声明改为implemention,然后同步项目即可

1
2
3
4
5
classpath 'com.android.tools.build:gradle:3.4.3'

改为

implemention 'com.android.tools.build:gradle:3.4.3'

调用 apply plugin: 'com.android.application'发生了什么?

升级项目至AndroidX,gradle.properties会添加如下配置,这些配置到底是如何工作的?

1
2
3
4
# Android 插件会使用对应的 AndroidX 库而非支持库。
android.useAndroidX=true
# Android 插件会通过重写现有第三方库的二进制文件,自动将这些库迁移为使用 AndroidX。
android.enableJetifier=true

[迁移AndroidX实践及Jetifier源码分析]https://yuweiguocn.github.io/migrate-to-androidx/