Gradle for Android 问题总结
Gradle是什么?
Gradle 是以Groovy为基础,面向java应用,基于DSL语法的自动化构建工具。是google引入,替换ant和maven的新工具,其依赖兼容maven和ivy。
使用gradle的目的:
更容易重用资源和代码;
可以更容易创建不同的版本的程序,多个类型的apk包;
更容易配置,扩展;
更好的IDE集成;
首先明确gradle跟maven一样,也有一个配置文件,maven里面是叫pom.xml,而在gradle中是叫build.gradle。Android Studio中的android项目通常至少包含两个build.gradle文件,一个是project范围的,另一个是module范围的,由于一个project可以有多个module,所以每个module下都会对应一个build.gradle。这么说有点抽象,看下面这个图:
1. Project中build.gradle
project下的build.gradle是基于整个project的配置,主要配置gradle 版本及 全局依赖仓库、库或者其他全部参数。
android studio 现在重要仓库采用jcenter(),之前版本放在mavenCentral。
另外有时还没有加入jcenter()仓库的第三方库,也需要在这里配置他们的库地址。
需要在这里配置,才能将第三方库拉下来
buildscript {
//构建过程依赖的仓库
repositories {
jcenter()
}
//构建过程需要依赖的库
dependencies {
//声明的是gradle插件的版本
classpath 'com.android.tools.build:gradle:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
//这里面配置整个项目依赖的仓库,这样每个module就不用配置仓库了
repositories {
jcenter()
maven {
// LeanCloud 的包仓库
url "http://mvn.leancloud.cn/nexus/content/repositories/releases"
}
}
}
//配置全局变量
ext {
// module依赖库公共版本号
SupportXVersion = '23.2.0'
GsonVersion = '2.6.2'
LeanCloudVersion = 'v3.13.4'
JunitVersion = '4.12'
compileSdkVersion = 22
buildToolsVersion = "23.0.1"
minSdkVersion = 10
targetSdkVersion = 22
versionCode = 34
versionName = "v2.6.1"
}
注:大家可能很奇怪,为什么仓库repositories需要声明两次,这其实是由于它们作用不同,buildscript中的仓库是gradle脚本自身需要的资源,而allprojects下的仓库是项目所有模块需要的资源
2. module中build.gradle
//申明使用插件,表明要编译的内容和产物,
//com.android.application 表明该module 为android 应用
//com.android.library 表明为library库
//java 表名是java库
apply plugin: 'com.android.application'
//安卓构建过程需要配置的参数
android {
//编译SDK的版本
compileSdkVersion COMPILE_SDK_VERSION as int
//buildtool 的版本
buildToolsVersion BUILD_TOOLS_VERSION
/默认配置,会同时应用到debug和release版本上
defaultConfig {
//应用包名
applicationId APPLICATION_ID
//支持最小android sdk 版本
minSdkVersion MIN_SDK_VERSION as int
// 目标版本
targetSdkVersion TARGET_SDK_VERSION as int
//应用版本号
versionCode VERSION_CODE as int
//应用版本名称
versionName VERSION_NAME
/// 配置生成的 BuildConfig 文件中的常量,代码引用直接
buildConfigField "String", "LOG_TAG", LOG_TAG // 日志tag
buildConfigField "String", "LOG_HTTP_TAG", LOG_TAG_HTTP // http日志tag
buildConfigField "String", "LOG_WEB_TAG", LOG_TAG_WEB // web日志tag
// 默认是UMENG_CHANNEL_VALUE为umeng
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
// dex突破65535的限制
multiDexEnabled true
}
//java版本号
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
//签名
signingConfigs {
release {
//签名文件
storeFile file(STORE_FILE)
storePassword STORE_PASSWORD
keyAlias KEY_ALIAS
keyPassword KEY_PASSWORD
}
}
// 为了解决部分第三方库重复打包了META-INF的问题
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
//移除lint检测的error
lintOptions {
abortOnError false
}
//编译类型
//其中debug, release是gradle默认自带的两个build type, 当然你可以定义其他类型。
//可以针对不停编译的版本中配置不同的参数,比如混淆、签名等。preview
buildTypes {
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
}
preview {
debuggable false // 是否保留调试信息
minifyEnabled true //是否混淆
zipAlignEnabled true // 包优化
shrinkResources true // 移除不必要的资源
// 签名
signingConfig signingConfigs.release
// 代码混淆规则文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
//加上后缀
applicationIdSuffix ".release"
minifyEnabled true //是否混淆
zipAlignEnabled true // zip对齐优化
shrinkResources true // 移除不必要的资源
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
// 签名
signingConfig signingConfigs.release
//混淆文件的位置
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 多渠道
productFlavors {
//可以设置不同渠道渠道号,应用名称
dev { // 开发
buildConfigField "String", "CHANNEL_NUMBER", '"11111"'
}
'360' {
buildConfigField "String", "CHANNEL_NUMBER", '"11112"'
}
GooglePlay {
buildConfigField "String", "CHANNEL_NUMBER", "11113"'
}
// 多渠道批量替换
productFlavors.all { flavor ->
//批量修改Manifest占位符替换
//在Manifest使用`${UMENG_CHANNEL_VALUE}`,`LEANCLOUD_CHANNEL_VALUE`,打包时将替换成渠道名,例如UMENG_CHANNEL_VALUE="dev";
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name, LEANCLOUD_CHANNEL_VALUE: name]
// Project Properties->_myAPPBuildVersionName,用于程序集成下命令行修改
if (project.hasProperty('_myAPPBuildVersionName')) {
defaultConfig.versionName = _myAPPBuildVersionName
}
}
//定义变量
//gradle 可以用def定义一些值例如:def KeyPassword = "123123"
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
// 批量打包
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName
if (variant.buildType.name.equals('release')) {
// myAPP_v版本号_渠道名.apk
fileName = "myAPP_v${variant.versionName}_${variant.productFlavors[0].name}.apk"
} else {
// myAPP_v版本号_渠道名_时间_编译类型名.apk
fileName = "myAPP_v${variant.versionName}_${variant.productFlavors[0].name}_${releaseTime()}_${variant.buildType.name}.apk"
}
output.outputFile = new File(outputFile.parent + "/${variant.buildType.name}", fileName)
}
}
}
}
//依赖第三方库
dependencies {
//编译libs目录下所以jar包
//compile files('libs/xxx.jar') 导入一个特定jar包
compile fileTree(dir: 'libs', include: ['*.jar'])//导入所有的jar包
compile project(':core')
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.android.support:cardview-v7:23.1.1'
}
注意:
- buildToolsVersion这个需要你本地安装该版本才行,很多人导入新的第三方库,失败的原因之一是build version的版本不对,这个可以手动更改成你本地已有的版本或者打开 SDK Manager 去下载对应版本。
- proguardFiles这部分有两段,前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,免去了我们很多事,这个文件的目录在 /tools/proguard/proguard-android.txt , 后一部分是我们项目里的自定义的混淆文件,目录就在 app/proguard-rules.pro ,在这个文件里你可以声明一些第三方依赖的一些混淆规则,最终混淆的结果是这两部分文件共同作用的。
- 一般重要的信息,例如签名信息,可以直接将信息写到gradle.properties,然后在然后在build.gradle中引用即可。
- 多渠道的关键在于定义不同的product flavor。
注意:这里的flavor名如果是数字开头,必须用引号引起来。 -
buildTypes
是指建构的类型,一般只用两种默认类型 debug 和 release ,顾名思义 debug 用来配置开发过程中的一些内容;release 用来配置正式发布版本的内容。有时我们需要发布介于debug与release之间的preview 版本。
3. Project中setting.gradle
这个文件是全局的项目配置文件,里面主要声明Project中所包括的所有module,
//一个Project中所包括的所有module
include ':app', ':model',':lib', ':core'
4. Project中gradle.properties
gradle.properties为gradle的配置文件,里面可以定义一些常量供build.gradle使用,比如可以配置签名相关信息如keystore位置,密码,keyalias等,build.gradle就可以直接引用
gradle 中的一些配置参数建议写到gradle.properties
//编译版本信息
APPLICATION_ID = com.jin.myAPP
COMPILE_SDK_VERSION = 23
BUILD_TOOLS_VERSION = 23.0.1
MIN_SDK_VERSION = 15
TARGET_SDK_VERSION = 1
VERSION_CODE = 1
VERSION_NAME = 1.0.0.0
//keystore信息
STORE_FILE = ../app/mykey.keystore
STORE_PASSWORD = your password
KEY_ALIAS = your alias
KEY_PASSWORD = your password
5. ext配置全局参数
project的build.gradle中的ext可以为各位module进行全局配置参数,防止各个module之间的不统一,不可控。而且当我们升级sdk、build tool、target sdk等,几个module都要更改,非常的麻烦。
ext {
compileSdkVersion = 22
buildToolsVersion = "23.0.1"
minSdkVersion = 10
targetSdkVersion = 22
versionCode = 34
versionName = "v2.6.1"
}
然后在各自module的build.gradle中引用:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.xxx.xxx"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
}
}
6. resValue 定义资源。
例如resValue "string" 就是字符串资源,可以用R.String 来引用对应的字符串资源
android {
defaultConfig {
resValue "string", "build_time", buildTime()
resValue "string", "build_host", hostName()
resValue "string", "build_revision", revision()
}
}
def buildTime() {
return new Date().format("yyyy-MM-dd HH:mm:ss")
}
def hostName() {
return System.getProperty("user.name") + "@" +InetAddress.localHost.hostName
}
def revision() {
def code = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--short', 'HEAD'
standardOutput = code
}
return code.toString()
}
上述代码实现了动态的添加了3个字符串资源: build_time、build_host、build_revision, 然后在其他地方可像如引用字符串一样使用如下:
// 在Activity里调用
getString(R.string.build_time) // 输出2015-11-07 17:01
getString(R.string.build_host) // 输出jay@deepin,这是我的电脑的用户名和PC名
getString(R.string.build_revision) // 输出3dd5823, 这是最后一次commit的sha值
7. PlaceHolder
manifest的一些值我们可以用PlaceHolder处理。例如
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
在gradle中改成
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "360"]
可以放在defaultConfig
中设置默认值,或者放在productFlavors
中根据不同渠道修改成不同值。或者批量修改
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
8. 导入某个project
如果你的app是多模块的,假设有两个模块app和lib,并且app模块是依赖lib的,这时候我们就需要在app模块的build.gradle中的dependencies结点下配置依赖:
compile project(':lib')
并且你需要在settings.gradle中把lib模块包含进来:
include ':lib',':app'
此外,这种情况下lib模块是作为库存在的,因而它的build.gradle中的插件声明通常应该是这样的:
apply plugin: 'com.android.library'
而且,作为library的模块lib的build.gradle文件的defaultConfig中是不允许声明applicationId的,这点需要注意。
9. 引用本地aar:
- 首先将你的库通过android studio 运行打包成aar文件。运行后在/build/output/aar文件夹中。
- 首先将aar文件放到模块的libs目录下,然后在该模块的build.gradle中声明flat仓库:
repositories{
flatDir {
dirs 'libs'
}
}
- 最后在dependencies结点下依赖该aar模块:
dependencies{
compile (name:'xxx',ext:'aar')
}
10. BuildConfig
在build.gradle中配置buildConfigField参数,编译后会在..\app\build\generated\source\buildConfig文件夹下会自动生成对应版本对应module的BuildConfig.java。BuildConfig就会包含对应版本的配置信息。程序中可以直接引用这些数据。例如BuildConfig.DEBUG。
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "360";
public static final int VERSION_CODE = 45;
public static final String VERSION_NAME = "3.2.0.0";
// Fields from build type: debug
public static final String HOST_IMG_SERVER = "img";
public static final String HOST_SERVER = "test";
// Fields from product flavor: 360
public static final String CHANNEL_NUMBER = "232100";
}
11. module 调整目录结构sourceSets
默认情况下,java文件和resource文件分别在src/main/java和src/main/res目录下,在build.gradle文件,andorid{}里面添加下面的代码,便可以将java文件和resource文件放到src/java和src/resources目录下。
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}
更简便的写法是:
sourceSets {
min.java.srcDirs = ['src/java']
min.resources.srcDirs = ['src/resources']
}
12. Gradle常用命令
上面大家接触了一些命令如 ./gradlew -v ./gradlew clean ./gradlew build, 这里注意是./gradlew, ./代表当前目录,gradlew代表 gradle wrapper,意思是gradle的一层包装,大家可以理解为在这个项目本地就封装了gradle,即gradle wrapper, myAPP/gradle/wrapper/gralde-wrapper.properties**文件中声明了它指向的目录和版本。只要下载成功即可用grdlew wrapper的命令代替全局的gradle命令。
理解了gradle wrapper的概念,下面一些常用命令也就容易理解了。
- ./gradlew 下载更新gradle
- ./gradlew -v 版本号
- ./gradlew assemble 构建项目输出
- ./gradlew check 运行检测和测试任务
- ./gradlew clean 清除9GAG/app目录下的build文件夹
- ./gradlew build 运行check和assemble,检查依赖并编译打包
这里注意的是 ./gradlew build 命令把debug、release环境的包都打出来,如果正式发布只需要打Release的包,该怎么办呢,下面介绍一个很有用的命令 assemble<build type="" name="">, 如</build> - ./gradlew assembleDebug 编译并打Debug包
- ./gradlew assembleRelease 编译并打Release的包所有渠道的
- ./gradlew assembleWandoujiaRelease 编译并打包豌豆荚的Release版本
- ./gradlew assembleWandoujia 编译并打包豌豆荚的所有版本
- ./gradlew installRelease Release模式打包并安装
- ./gradlew uninstallRelease 卸载Release模式包