湖畔镇

AndroidStudio——Gradle小贴士

AndroidStudio里使用Gradle的一些小知识

管理工程和资源

改变默认源集

修改sourceSets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
sourceSets {
main {
java.srcDirs = ['other/java']
res.srcDirs = ['other/res1', 'other/res2']
manifest.srcFile 'other/AndroidManifest.xml'
...
}

androidTest {
setRoot 'src/tests'
...
}
}
}

配置工程范围的属性

在顶级build.gradle中添加ext

1
2
3
4
5
6
7
8
buildscript {...}
allprojects {...}

ext {
compileSdkVersion = 25
buildToolsVersion = "25.0.0"
supportLibVersion = "25.2.0"
}

使用rootProject.ext.property_name访问这些属性

1
2
3
4
5
6
7
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
dependencies {
compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
}

管理库和依赖

使用依赖配置的特定目标构建

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

configurations {
freeDebugApk {}
...
}

dependencies {
freeCompile 'com.google.firebase:firebase-ads:9.8.0'
freeDebugApk fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}

发布库的非默认变体

改变默认构建变体

1
2
3
4
android {
...
defaultPublishConfig "demoDebug"
}

发布所有库的可用构建变体

1
2
3
android {
publishNonDefault true
}

配置应用模块的构建变体,使他们只使用对应的库的构建变体

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

configurations {
demoDebugCompile {}
fullReleaseCompile {}
...
}

dependencies {
demoDebugCompile project(path: ':my-library-module', configuration: 'demoDebug')
fullReleaseCompile project(path: ':my-library-module', configuration: 'fullRelease')
...
}

创建应用的不同版本

配置多APK支持

配置每个分辨率一个单独APK

添加android.splits.density

1
2
3
4
5
6
7
8
9
10
11
12
13
android {
...
splits {
density {
enable true
exclude "ldpi", "mdpi"
// 清理并包括
// reset()
// include "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
compatibleScreens 'normal', 'large', 'xlarge'
}
}
}

配置每个ABI一个单独APK

添加android.splits.abi

1
2
3
4
5
6
7
8
9
10
11
12
13
android {
...
splits {
abi {
enable true
// 默认所有ABI都包括,清理
reset()
include "x86", "armeabi-v7a", "mips"
// 需要生成一个包括所有ABI的APK
universalApk true
}
}
}

配置动态版本号

默认每个APK都会有同样的版本信息,谷歌商店不支持同一个应用的不同APK有同样的版本信息,你需要确保每个APK有自己独特的versionCode

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
android {
defaultConfig {
...
versionCode 4
}
splits {
...
}
}

// Map for the version code that gives each ABI a value.
ext.abiCodes = ['armeabi-v7a':1, mips:2, x86:3]

// For per-density APKs, create a similar map like this:
// ext.densityCodes = ['hdpi': 1, 'xhdpi': 2, 'xxhdpi': 3, 'xxxhdpi': 4]

import com.android.build.OutputFile

// For each APK output variant, override versionCode with a combination of
// ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
android.applicationVariants.all { variant ->

// Assigns a different version code for each output APK
// other than the universal APK.
variant.outputs.each { output ->
// Stores the value of ext.abiCodes that is associated with the ABI for this variant.
// Determines the ABI for this variant and returns the mapped value.
def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
// Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
// the following code does not override the version code for universal APKs.
// However, because we want universal APKs to have the lowest version code,
// this outcome is desirable.
if (baseAbiVersionCode != null) {
// Assigns the new version code to versionCodeOverride, which changes the version code
// for only the output APK, not for the variant itself. Skipping this step simply
// causes Gradle to use the value of variant.versionCode for the APK.
output.versionCodeOverride = baseAbiVersionCode * 1000 + variant.versionCode
}
}
}

配置多个产品风味

通过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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
android {
...
buildTypes {
debug {...}
release {...}
}

// Specifies the flavor dimensions you want to use. The order in which you
// list each dimension determines its priority, from highest to lowest,
// when Gradle merges variant sources and configurations. You must assign
// each product flavor you configure to one of the flavor dimensions.
flavorDimensions "api", "mode"

productFlavors {
demo {
// Assigns this product flavor to the "mode" flavor dimension.
dimension "mode"
...
}

full {
dimension "mode"
...
}

// Configurations in the "api" product flavors override those in "mode"
// flavors and the defaultConfig block. Gradle determines the priority
// between flavor dimensions based on the order in which they appear next
// to the flavorDimensions property above--the first dimension has a higher
// priority than the second, and so on.
minApi24 {
dimension "api"
minSdkVersion '24'
// To ensure the target device receives the version of the app with
// the highest compatible API level, assign version codes in increasing
// value with API level. To learn more about assigning version codes to
// support app updates and uploading to Google Play, read Multiple APK Support
versionCode 30000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi24"
...
}

minApi23 {
dimension "api"
minSdkVersion '23'
versionCode 20000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi23"
...
}

minApi21 {
dimension "api"
minSdkVersion '21'
versionCode 10000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi21"
...
}
}
}

过滤器

通过variantFilter配置不希望构建的变体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
android {
...
buildTypes {...}

flavorDimensions "api", "mode"
productFlavors {
demo {...}
full {...}
minApi24 {...}
minApi23 {...}
minApi21 {...}
}

variantFilter { variant ->
def names = variant.flavors*.name
// To check for a build type instead, use variant.buildType.name == "buildType"
if (names.contains("minApi21") && names.contains("demo")) {
// Gradle ignores any variants that satisfy the conditions above.
setIgnore(true)
}
}
}

测试应用

配置Lint

使用lintOptions配置Lint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
android {
...
lintOptions {
// Turns off checks for the issue IDs you specify.
disable 'TypographyFractions','TypographyQuotes'
// Turns on checks for the issue IDs you specify. These checks are in
// addition to the default lint checks.
enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
// To enable checks for only a subset of issue IDs and ignore all others,
// list the issue IDs with the 'check' property instead. This property overrides
// any issue IDs you enable or disable using the properties above.
check 'NewApi', 'InlinedApi'
// If set to true, turns off analysis progress reporting by lint.
quiet true
// if set to true (default), stops the build if errors are found.
abortOnError false
// if true, only report errors.
ignoreWarnings true
}
}
...

配置测试manifest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
android {
...
// Each product flavor you configure can override properties in the
// defaultConfig block. To learn more, go to Configure Product Flavors.
defaultConfig {
...
// Specifies the application ID for the test APK.
testApplicationId "com.test.foo"
// Specifies the fully-qualified class name of the test instrumentation runner.
testInstrumentationRunner "android.test.InstrumentationTestRunner"
// If set to 'true', enables the instrumentation class to start and stop profiling.
// If set to false (default), profiling occurs the entire time the instrumentation
// class is running.
testHandleProfiling true
// If set to 'true', indicates that the Android system should run the instrumentation
// class as a functional test. The default value is 'false'
testFunctionalTest true
}
}
...

改变测试构建类型

1
2
3
4
android {
...
testBuildType "staging"
}

配置Gradle测试选项

通过testOptions配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
...
// Encapsulates options for running tests.
testOptions {
// Changes the directory where Gradle saves test reports. By default, Gradle saves test reports
// in the path_to_your_project/module_name/build/outputs/reports/ directory.
// '$rootDir' sets the path relative to the root directory of the current project.
reportDir "$rootDir/test-reports"
// Changes the directory where Gradle saves test results. By default, Gradle saves test results
// in the path_to_your_project/module_name/build/outputs/test-results/ directory.
// '$rootDir' sets the path relative to the root directory of the current project.
resultsDir "$rootDir/test-results"
}
}

通过testOptions.unitTests块仅配置单元测试

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
27
28
29
30
android {
...
testOptions {
...
// Encapsulates options for unit tests.
unitTests {
// By default, unit tests throw an exception any time the code you are testing tries to access
// Android platform APIs (unless you mock Android dependencies yourself or with a testing
// framework like Mockito). However, you can enable the following property so that the test
// returns either null or zero when accessing platform APIs, rather than throwing an exception.
returnDefaultValues true

// Encapsulates options for controlling how Gradle executes unit tests. For a list
// of all the options you can specify, read Gradle's reference documentation.
all {
// Sets JVM argument(s) for the test JVM(s).
jvmArgs '-XX:MaxPermSize=256m'

// You can also check the task name to apply options to only the tests you specify.
if (it.name == 'testDebug') {
systemProperty 'debug', 'true'
}

if (it.name == 'connectedDebugAndroidTest') {
...
}
}
}
}
}

优化构建

裁剪代码

1
2
3
4
5
6
7
8
9
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
...
}

为不同的构建提供裁剪的`proguar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
flavor1 {
...
}
flavor2 {
proguardFile 'flavor2-rules.pro'
}
}
}

配置DEX

1
2
3
4
5
6
7
8
9
10
11
12
13
android {
...
dexOptions {
// Sets the maximum number of DEX processes
// that can be started concurrently.
maxProcessCount 8
// Sets the maximum memory allocation pool size
// for the dex operation.
javaMaxHeapSize "2g"
// Enables Gradle to pre-dex library dependencies.
preDexLibraries true
}
}

发布应用

签名

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
android {
...
defaultConfig { ... }

// Encapsulates signing configurations.
signingConfigs {
// Creates a signing configuration called "release".
release {
// Specifies the path to your keystore file.
storeFile file("my-release-key.jks")
// Specifies the password for your keystore.
storePassword "password"
// Specifies the identifying name for your key.
keyAlias "my-alias"
// Specifies the password for your key.
keyPassword "password"
}
}
buildTypes {
release {
// Adds the "release" signing configuration to the release build type.
signingConfig signingConfigs.release
...
}
}
}

从工程中移除私人签名信息

  1. 创建keystore.properties目录
1
2
3
4
storePassword=[myStorePassword]
keyPassword=[myKeyPassword]
keyAlias=[myKeyAlias]
storeFile=[myStoreFileLocation]
  1. build.gradle中加载keystore.properties文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Creates a variable called keystorePropertiesFile, and initializes it to the
// keystore.properties file.
def keystorePropertiesFile = rootProject.file("keystore.properties")

// Initializes a new Properties() object called keystoreProperties.
def keystoreProperties = new Properties()

// Loads the keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
signingConfigs {
config {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
...
}

简化应用开发

与代码共享自定义域和资源值

编译时,生成BuildConfig类,所以代码可以获得当前构建的信息,可以在buildConfigField()方法中添加自定义域,在运行时访问,也可以通过resValue()添加资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
android {
...
buildTypes {
release {
// These values are defined only for the release build, which
// is typically used for full builds and continuous builds.
buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
resValue("string", "build_time", "${minutesSinceEpoch}")
...
}
debug {
// Use static values for incremental builds to ensure that
// resource files and BuildConfig aren't rebuilt with each run.
// If they were dynamic, they would prevent certain benefits of
// Instant Run as well as Gradle UP-TO-DATE checks.
buildConfigField("String", "BUILD_TIME", "\"0\"")
resValue("string", "build_time", "0")
}
}
}

可以这样访问他们

1
2
Log.i(TAG, BuildConfig.BUILD_TIME);
Log.i(TAG, getString(R.string.build_time));

与Manifest共享属性

有时需要在代码和Manifest中声明同样的属性值,在build.gradle中定义一份多处使用最好

1
2
3
4
5
6
7
8
9
10
11
12
13
android {
// For settings specific to a product flavor, configure these properties
// for each flavor in the productFlavors block.
defaultConfig {
// Creates a property for the FileProvider authority.
def filesAuthorityValue = applicationId + ".files"
// Creates a placeholder property to use in the manifest.
manifestPlaceholders = [filesAuthority : filesAuthorityValue]
// Adds a new field for the authority to the BuildConfig class.
buildConfigField("String", "FILES_AUTHORITY", "\"${filesAuthorityValue}\"")
}
...
}

Manifest中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${filesAuthority}"
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
</application>
</manifest>

代码中使用

1
Uri contentUri = FileProvider.getUriForFile(getContext(), BuildConfig.FILES_AUTHORITY, myFile);
分享