Gradle系列运用篇

4次阅读

共计 9132 个字符,预计需要花费 23 分钟才能阅读完成。

上次我们说到 gradle 的原理,主要是偏理论上的知识点,直通车在这 Android Gradle 系列 - 原理篇。这次我们来点实战的,随便巩固下之前的知识点。

android

在 app module 下的 gradle.build 中都有一个 android 闭包,主要配置都在这里设置。例如默认配置项:defaultConfig;签名相关:signingConfig;构建变体:buildTypes;产品风格:productFlavors;源集配置:sourceSets 等。

defaultConfig

对于 defaultConfig 其实它是也一个 productFlavor,只不过这里是用来提供默认的设置项,如果之后的 productFlavor 没有特殊指定的配置都会使用 defaultConfig 中的默认配置。

public class DefaultConfig extends BaseFlavor {
    @Inject
    public DefaultConfig(
            @NonNull String name,
            @NonNull Project project,
            @NonNull ObjectFactory objectFactory,
            @NonNull DeprecationReporter deprecationReporter,
            @NonNull Logger logger) {super(name, project, objectFactory, deprecationReporter, logger);
    }
}
 
public abstract class BaseFlavor extends DefaultProductFlavor implements CoreProductFlavor {...}

可以看到 defaultConfig 的超级父类就是 DefaultProductFlavor。而在 DefaultProductFlavor 中定义了许多我们经常见到的配置:VersionCode、VersionName、minSdkVersion、targetSdkVersion 与 applicationId 等等。

有了上面的基础,那么在 defaultConfig 中我们要配置的变量就显而易见了。

    defaultConfig {
        applicationId "com.idisfkj.androidapianalysis"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

signingConfigs

signingConfig 是用来配置 keyStore,我们可以针对不同的版本配置不同的 keyStore,例如

    signingConfigs {
        config { // 默认配置
            storeFile file('key.store')
            storePassword 'android123'
            keyAlias 'android'
            keyPassword 'android123'
        }
        dev { //dev 测试版配置
            storeFile file('xxxx')
            storePassword 'xxx'
            keyAlias 'xxx'
            keyPassword 'xxx'
        }
    }

有人可能会说这不安全的,密码都是明文,都暴露出去了。是的,如果这项目发布到远程,那么这些秘钥就泄露出去了。所以为了安全起见,我们可以对其进一些特殊处理。

  1. 通过环境变量获取秘钥
storePassword System.getenv("KSTOREPWD")
keyPassword System.getenv("KEYPWD")
  1. 从命令行中获取秘钥
storePassword System.console().readLine("\nKeystore password:")
keyPassword System.console().readLine("\nKey password:")

上面两种是 Android Develop 官网提供的,但经过测试都会报 null 异常,查了下资料都说是 gradle 不支持(如果有成功的可以告知我),所以还是推荐下面的这种方法

在项目的根目录下 (settings.gradle 平级) 创建 keystore.properties 文件,我们在这个文件中进行存储秘钥,它是支持 key-value 模式的键值对数据

storePassword = android123
keyPassword = android123

之后就是读取其中的 password,在 build.gradle 通过 afterEvaluate 回调进行读取与设置

afterEvaluate {def propsFile = rootProject.file('keystore.properties')
    def configName = 'config'
    if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) {def props = new Properties()
        props.load(new FileInputStream(propsFile))
        android.signingConfigs[configName].keyPassword = props['keyPassword']
        android.signingConfigs[configName].storePassword = props['storePassword']
    }
}

我们已经通过动态读取了 password,所以在之前的 signingConfigs 中就无需再配置 password

    signingConfigs {
        config {storeFile file('key.store')
            keyAlias 'android'
        }
    }

最后一步,为了保证秘钥的安全性,在.gitignore 中添加 keystore.properties 的忽略配置,防止上传到远程仓储暴露秘钥。

buildTypes

构建变体主要用来配置 shrinkResources:资源是否需要压缩、zipAlignEnabled:压缩是否对齐、minifyEnabled:是否代码混淆与 signingConfig:签名配置等等。新建项目时,默认有一个 release 配置,但我们实际开发中可能需要多个不同的配置,例如 debug 模式,为了方法调试,一般都不需要对其进行代码混淆、压缩等处理。或者 outer 模式,需要的签名配置不同,所以最终的配置可以是这样:

    buildTypes {
        debug {
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.config
        }
        outer {
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.outConfig
        }
        release {
            minifyEnabled true
            zipAlignEnabled true
            shrinkResources true
            signingConfig signingConfigs.config
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

Sync Now 之后,打开 Android Studio 右边的 Gradle,找到 app->Tasks->build,发现已经添加了 assembleDebug 与 assembleOuter 构建 task。

productFlavors

一个项目可能有不同的版本环境,例如开发功能中的开发版、项目上线的正式版。开发版与正式版请求的数据 api 可能不同,对于这种情况我们就可以使用 productFlavor 来构建不同的产品风格,可以看下面的 dev 与 prod 配置

    flavorDimensions "mode"
    productFlavors {
        dev {
            applicationIdSuffix ".dev"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name_dev",
                                    APP_ID      : "21321843"]
            buildConfigField 'String', 'API_URL', '"https://dev.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"3824yk32"'
        }
        prod {
            applicationIdSuffix ".prod"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name",
                                    APP_ID      : "12932843"]
            buildConfigField 'String', 'API_URL', '"https://prod.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"32143dsk2"'
        }
    }

对于判断是否为同一个 app,手机系统是根据 app 的 applicationId 来识别的,默认 applicationId 是 packageName。所以为了让 dev 与 prod 的版本都能共存在一个手机上,可以通过 applicationIdSuffix 来为 applicationId 增加后缀,改变安装包的唯一标识。

还有可以通过 manifestPlaceholders 来配置可用于 AndroidManifest 中的变量,例如根据不同的产品风格显示不同的 app 名称

dev 与 prod 网络请求时使用不同的 api host,可以设置 buildConfigField,这样我们就可以在代码中通过 BuildConfig 获取

    fun getApiUlr(): String {return BuildConfig.API_URL}

这里的 BuildConfig 会根据你构建的产品风格返回不同的值,它位于 build->generated->source->buildConfig-> 变体,大致内容如下:

public final class BuildConfig {public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.idisfkj.androidapianalysis.dev";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "devMinApi21";
  public static final int VERSION_CODE = 20001;
  public static final String VERSION_NAME = "1.0-minApi21";
  public static final String FLAVOR_mode = "dev";
  public static final String FLAVOR_api = "minApi21";
  // Fields from product flavor: dev
  public static final String API_URL = "https://dev.idisfkj.android.com";
  public static final String APP_KEY = "3824yk32";
}

Sync Now 之后,打开 Android Studio 右边的 Gradle,找到 app->Tasks->build,发现新添加了 assembleDev 与 assembleProd 构建 task。

flavorDimensions 是用来设置多维度的,上面的例子只展示了一个维度,所以 dimension 为 mode 的形式。我们新增一个 api 维度,构建不同的 minSkdVerison 版本的 apk

    flavorDimensions "mode", "api"
    productFlavors {
        dev {
            applicationIdSuffix ".dev"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name_dev",
                                    APP_ID      : "21321843"]
            buildConfigField 'String', 'API_URL', '"https://dev.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"3824yk32"'
        }
        prod {
            applicationIdSuffix ".prod"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name",
                                    APP_ID      : "12932843"]
            buildConfigField 'String', 'API_URL', '"https://prod.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"32143dsk2"'
        }
        minApi16 {
            dimension "api"
            minSdkVersion 16
            versionCode 10000 + android.defaultConfig.versionCode
            versionNameSuffix "-minApi16"
        }
        minApi21 {
            dimension "api"
            minSdkVersion 21
            versionCode 20000 + android.defaultConfig.versionCode
            versionNameSuffix "-minApi21"
        }
    }

gradle 创建的构建变体数量等于每个风格维度中的风格数量与你配置的构建类型数量的乘积,所以上面例子的构建变体数量为 12 个。在 gradle 为每个构建变体或对应 apk 命名时,属于较高优先级风格维度的产品风格首先显示,之后是较低优先级维度的产品风格,再之后是构建类型。而优先级的判断则以 flavorDimensions 的值顺序为依据,以上面的构建配置为例:

构建变体:dev, prod[debug, outer, release]
对应 apk:app-[dev, prod]-[minApi16, minApi21]-[debug, outer, release].apk

构建变体有这么多,但有时我们并不全部需要,例如你不需要 mode 为 dev,api 为 minApi16 的变体,这时你就可以使用 variantFilter 方法来过滤

    variantFilter { variant ->
        def names = variant.flavors*.name
        if (names.contains("minApi16") && names.contains("dev")) {setIgnore(true)
        }
    }

你再回到 app->Tasks 中查看变体,会发现已经将 devMinApi16 相关的变体过滤了。

你不仅可以过滤构建变体,还可以改变默认的 apk 输出名称。例如你想修改 buildType 为 release 的 apk 名称,这时你可以使用 android.applicationVariants.all

    android.applicationVariants.all { variant ->
        if (variant.buildType.name == buildTypes.release.name) {
            variant.outputs.all {outputFileName = "analysis-release-${defaultConfig.versionName}.apk"
            }
        }
    }

这样在 release 下的包名都是以 analysis 打头

sourceSets

Android Studio 会帮助我们创建默认的 main 源集与目录(位于 app/src/main),用来存储所有构建变体间的共享资源。所以你可以通过设置 main 源集来更改默认的配置。例如现在你想将 res 的路径修改成 src/custom/res

    sourceSets {
        main {res.srcDirs = ['src/custom/res']
        }
    }

这样 res 资源路径就定位到了 src/custom/res 下,当然你也可以修改其它的配置,例如 java、assets、jni 等。

如果你配置了多个路径,即路径集合:

    sourceSets {
        main {res.srcDirs = ['src/custom/res', 'scr/main/res']
        }
    }

这时你要保证不能有相同的名称,即每个文件只能唯一存在其中一个目录下。

你也可以查看所以的构建变体的默认配置路径: 点击右边 gradle->app->android->sourceSets,你将会看到如下类似信息

------------------------------------------------------------
Project :app
------------------------------------------------------------
 
androidTest
-----------
Compile configuration: androidTestCompile
build.gradle name: android.sourceSets.androidTest
Java sources: [app/src/androidTest/java]
Manifest file: app/src/androidTest/AndroidManifest.xml
Android resources: [app/src/androidTest/res]
Assets: [app/src/androidTest/assets]
AIDL sources: [app/src/androidTest/aidl]
RenderScript sources: [app/src/androidTest/rs]
JNI sources: [app/src/androidTest/jni]
JNI libraries: [app/src/androidTest/jniLibs]
Java-style resources: [app/src/androidTest/resources]
...

上面是 androidTest 变体的默认路径,首先它会去查找相应的构建变体的默认位置,如果没有找到,就会使用 main 源集下的默认配置。也就是我们所熟悉的 app/src/main 路径下的资源。

因为它是跟构建变体来搜索的,所以它有个优先级:

  1. src/modeApiDebug: 构建变体
  2. src/debug:构建类型
  3. src/modeApi:产品风格
  4. src/main:默认 main 源

对于源集的创建,如下所示在 app/src 下右键新建,但它只会帮你创建源集下的 java 文件夹,其它的都要你自己逐个创建

我们自定义一个 debug 源集,所以进去之后 Target Source Set 选择 debug,再点击 finish 结束。这时你将会在 src 下看到 debug 文件夹

现在你已经有了 debug 的源集目录,假设你现在要使 debug 下的 app 名称展示成 Android 精华录 debug(默认是 Android 精华录)。这时你可以右键 debug 新建 values

在 values 目录下新建 strings.xml,然后在其中配置 app_name

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Android 精华录 debug</string>
</resources>

最后你再去构建 debug 相关的变体时,你安装的 app 展示的名称将是 Android 精华录 debug。

所以通过修改 mian 源集或者配置其它的变体源集,可以实现根据变体加载不同的数据源。这样系统化的配置加载资源将更加方便项目测试与版本需要的配置。

dependencies

dependencies 闭包上用来配置项目的第三方依赖,如果你根据上面的配置有设置变体,那么你将可以根据变体来选择性的依赖第三方库

dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    // 根据变体选择性依赖
    outerImplementation '...'
    prodMinApi21Implementation '...'
}

关于 dependencies,这只是简单的配置方式,之后我还会单独抽出一篇文章来写系统化的配置 dependencies,感兴趣的可以关注下。

gradle 相关的配置还有很多,这里只是冰山一角,但我的建议是根据你的实际需求去学习与研究,相信你也会有意想不到的成长。

最后附上源码地址:https://github.com/idisfkj/an…

博客地址:https://www.rousetime.com/

系列

Android Gradle 系列 - 入门篇

Android Gradle 系列 - 原理篇

正文完
 0