AndroidStudio的编译系统编译app的资源文件和代码,打包为一个可测试、部署、签名、发布的apk。Android Studio使用一个高级的编译工具:gradle,该工具可以自由灵活的客制化,自动生成和管理编译进度。编译app,编译配置可以有公共部分,每一个编译配置文件也可以有自己单独的编译文件和资源。使用gradle和针对gradle的Android插件,和编译工具包共同提供编译进程和可配置的编译设置,这些说明app的编译和测试。
Gradle和Android插件独立于AndroidStudio运行。这意味着我们可以有多重方式编译app,如命令行、持续集成服务器、AndroidStudio.无论哪一种方式,都可以编译出相同的输出。
命令行编译,使用的是Gradle wrapper
命令行工具。命令在工程的根目录下执行。
gradlew task-name
例如:
D:\AndroidProjects\HalService>gradlew tasks
> Task :tasks
------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------
Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for the base and test modules
sourceSets - Prints out all the source sets defined in this project.
Build tasks
-----------
assemble - Assembles the outputs of this project.
assembleAndroidTest - Assembles all the Test applications.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
bundle - Assemble bundles for all the variants.
classes - Assembles main classes.
...
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
lintVitalRelease - Runs lint on just the fatal issues in the release build.
test - Runs the unit tests.
testDebugUnitTest - Run unit tests for the debug build.
testReleaseUnitTest - Run unit tests for the release build.
Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.
To see all tasks and more detail, run gradlew tasks --all
To see more detail about a task, run gradlew help --task <task>
gradlew assembleDebug
生成apk在project_name/module_name/build/outputs/apk/
目录下,该apk已使用默认debug签名和压缩对齐。
安装debug版本apk
gradlew installDebug
Debug
可以被替换为已定义的BuildType。debug是默认就有的编译类型,无需显示定义,其他类型需要显示定义。
清除编译缓存
gradlew cleanBuildCache
android {
...
defaultConfig { ... }
signingConfigs {
release {
// You need to specify either an absolute path or include the
// keystore file in the same directory as the build.gradle file.
storeFile file("my-release-key.jks")
storePassword "password"
keyAlias "my-alias"
keyPassword "password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
...
}
}
}
在配置文件中配置签名文件后,就可以使用
gradlew installRelease
编译release版本
自定义编译类型可以指定该编译类型具有的编译特性。例如增加编译类型staging
android {
defaultConfig {
manifestPlaceholders = [hostName:"www.example.com"]
...
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".debug"
debuggable true
}
/**
* The `initWith` property allows you to copy configurations from other build types,
* then configure only the settings you want to change. This one copies the debug build
* type, and then changes the manifest placeholder and application ID.
*/
staging {
initWith debug
manifestPlaceholders = [hostName:"internal.example.com"]
applicationIdSuffix ".debugStaging"
}
}
}
可配置的属性,详见“build type DSL reference”
不同风味的产品代表不同的版本release给用户使用,例如免费的和收费的。可以通过不同的代码和资源定义不同分为的版本软件。定义不同风味的版本,是可选功能,且只能手动创建。
创建不同风味的软件版本,和自定义编译类型方式类似。创建不同风味版本,在productFlavors块中添加需要的配置。风味的产品定义都支持如defaultConfig
的属性,该属性属于ProductFlavor
类。这意味着,不同风味产品的共有属性可以在defaultConfig中配置,各风味产品可以修改需要修改的属性。
每一个风味类型必须属于一个风味盒(a named flavor dimension),风味盒是不同风味产品的集合。所有的风味产品必须有一个风味盒,否则,编译会出错。如果给定的module只有一个风味盒,gradle插件会默认分配所有的module的风味到这个风味盒。
例:创建一个命名为“version”的风味盒且添加“demo”和“full”风味产品。
android {
...
defaultConfig {...}
buildTypes {
debug{...}
release{...}
}
// Specifies one flavor dimension.
flavorDimensions "version"
productFlavors {
demo {
// Assigns this product flavor to the "version" flavor dimension.
// If you are using only one dimension, this property is optional,
// and the plugin automatically assigns all the module's flavors to
// that dimension.
dimension "version"
applicationIdSuffix ".demo"
versionNameSuffix "-demo"
}
full {
dimension "version"
applicationIdSuffix ".full"
versionNameSuffix "-full"
}
}
}
创建完成不同风味产品配置后,点击Sync Now
,同步代码。同步完成后,gradle会自动根据编译类型和产品风味创建编译变体,命名为“”,例如,创建了demo、full两种风味产品,有默认的debug、release两种编译类型,gradle会创建一下编译变体:
可以通过Build > Select Build Variant 选择想要编译和运行的变体。
为了定制化不同编译变体,使用于不同的特性和资源,需要给不同的变体配置不同的资源,详见下文的“创建和管理资源集”。
在一些情况下,可能会需要根据不同风味合并成一个配置。例如,要创建不同配置的风味产品“fulll”、“demo”,每个风味产品又会基于不同的api创建。这种情况下,gradle插件提供可配置多个风味盒,编译时gradle会合并每个风味盒中的一个风味产品配置到一个配置,相同风味盒中的产品配置不会合并。风味产品配置合并完后和编译类型组合,形成不同的编译变体。(也即不同的风味盒中的风味产品配置、编译类型做组合)。通过flavorDimensions
定义不同的风味盒。
定义不同的风味盒,合并不同风味产品配置,形成不同编译变体,例如下:
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"
...
}
}
}
...
形成12中编译变体如下:
[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
生成apk有:
app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
具体编译结果如下:
Build variant: minApi24DemoDebug
Corresponding APK: app-minApi24-demo-debug.apk
可以为不同的风味产品创建目录,放置和该风味相关的资源文件,如src/demoMinApi24/java/
。具体不同编译变体如何组织和合并资源,详见下文的“创建资源集”。
若定义多个风味产品配置,gradle编译后组成多个编译变体,若其中一些变体是不需要的,可以通过过滤规则过滤掉不需要的变体。
例如不需要demo风味产品的API小于23的变体
android {
...
buildTypes {...}
flavorDimensions "api", "mode"
productFlavors {
demo {...}
full {...}
minApi24 {...}
minApi23 {...}
minApi21 {...}
}
variantFilter { variant ->
def names = variant.flavors*.name
// To check for a certain build type, use variant.buildType.name == ""
if (names.contains("minApi21") && names.contains("demo")) {
// Gradle ignores any variants that satisfy the conditions above.
setIgnore(true)
}
}
}
...
配置完成后,同步代码,gradle会忽略不需要的变体。
默认情况下,Android Studio创建了main/
资源集,该目录下的资源被所有变体共用。当然,我们也可以为具体的某个风味版本、编译类型、编译变体创建特有的资源集,如此情况下,在main/资源集下放置共用的基础功能资源,其他资源集按照关联特性放置资源。
Gradle规定新增的资源集需要按照main/资源集的组织结构。例如,为debug编译类型创建一个资源集,资源集目录为src/debug/java/
。
Android插件为Gradle提供了一个很好用的Gradle task功能,告诉我们怎样组织新增的资源集。如下例,使用Gradle task功能,可以输出一个描述文件,文件中告诉我们:新增一个debug编译类型的资源集,资源集中文件的组织结构应该是怎样的。
------------------------------------------------------------
Project :app
------------------------------------------------------------
...
debug
----
Compile configuration: compile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Manifest file: app/src/debug/AndroidManifest.xml
Android resources: [app/src/debug/res]
Assets: [app/src/debug/assets]
AIDL sources: [app/src/debug/aidl]
RenderScript sources: [app/src/debug/rs]
JNI sources: [app/src/debug/jni]
JNI libraries: [app/src/debug/jniLibs]
Java-style resources: [app/src/debug/resources]
注:文档中描述的生成该描述文件的方法,在最新版本的AndroidStudio中没有找到,这里不做说明。
当生成一个新的编译变体,Android Studio创建该变体的资源集,但提供了创建向导:
project
显示MyProject/app/src/
src
,New->Floder->[select source type]利用该向导可以创建需要的资源集。
如果创建了和Gradle默认定义的目录结构不同的资源集,这种情况可以在module下的build.gradle
配置资源集,配置使用的属性详见“Android plugin for Gradle DSL reference”,部分内容如下:
以下示例,改变main/
moudle的资源集内容和修改androidTest的根目录
android {
...
sourceSets {
// Encapsulates configurations for the main source set.
main {
// Changes the directory for Java sources. The default directory is
// 'src/main/java'.
java.srcDirs = ['other/java']
// If you list multiple directories, Gradle uses all of them to collect
// sources. Because Gradle gives these directories equal priority, if
// you define the same resource in more than one directory, you get an
// error when merging resources. The default directory is 'src/main/res'.
res.srcDirs = ['other/res1', 'other/res2']
// Note: You should avoid specifying a directory which is a parent to one
// or more other directories you specify. For example, avoid the following:
// res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
// You should specify either only the root 'other/res1' directory, or only the
// nested 'other/res1/layouts' and 'other/res1/strings' directories.
// For each source set, you can specify only one Android manifest.
// By default, Android Studio creates a manifest for your main source
// set in the src/main/ directory.
manifest.srcFile 'other/AndroidManifest.xml'
...
}
// Create additional blocks to configure other source sets.
androidTest {
// If all the files for a source set are located under a single root
// directory, you can specify that directory using the setRoot property.
// When gathering sources for the source set, Gradle looks only in locations
// relative to the root directory you specify. For example, after applying the
// configuration below for the androidTest source set, Gradle looks for Java
// sources only in the src/tests/java/ directory.
setRoot 'src/tests'
...
}
}
}
...
可以使用资源集放置不同的代码和资源。例如,如果编译“demo”、“debug”的变体“demoDebug”,编译资源为不同资源集的笛卡尔积(相同文件名使用高优先级的,不同的部分做并集处理),编译的优先级书序如下(从高到低):
“duplicate class”
,此时需要在各自的资源集中定义各自的文件,不要和main/资源集中的文件名相同。可以通过在Implementation前面加前缀为编译变体或资源集指明各自的依赖。例如
dependencies {
// Adds the local "mylibrary" module as a dependency to the "free" flavor.
freeImplementation project(":mylibrary")
// Adds a remote binary dependency only for local tests.
testImplementation 'junit:junit:4.12'
// Adds a remote binary dependency only for the instrumented test APK.
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
详见“Add build dependencies”
给apk签名有两种方式:
第一种方式不做说明,第二中方式如下:
build.gradle
增加如下配置:...
android {
...
defaultConfig {...}
signingConfigs {
release {
storeFile file("myreleasekey.keystore")
storePassword "password"
keyAlias "MyReleaseKey"
keyPassword "password"
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
}
直接把密码写在代码中不是一个安全的方式,可以通过以下方式获取密码:
storePassword System.getenv("KSTOREPWD")
keyPassword System.getenv("KEYPWD")
storePassword System.console().readLine("\nKeystore password: ")
keyPassword System.console().readLine("\nKey password: ")