Gradle系列-运用篇
上次我们说到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'
}
}
有人可能会说这不安全的,密码都是明文,都暴露出去了。是的,如果这项目发布到远程,那么这些秘钥就泄露出去了。所以为了安全起见,我们可以对其进一些特殊处理。
- 通过环境变量获取秘钥
storePassword System.getenv("KSTOREPWD")
keyPassword System.getenv("KEYPWD")
- 从命令行中获取秘钥
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][minApi16, minApi21][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路径下的资源。
因为它是跟构建变体来搜索的,所以它有个优先级:
- src/modeApiDebug: 构建变体
- src/debug:构建类型
- src/modeApi:产品风格
- 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/android-api-analysis