// 声明是Android程序
//apply plugin是最新gradle版本的写法,以前的写法是apply plugin: 'android', 如果还是以前的写法,请改正过来
apply plugin: 'com.android.application'
//自定义函数 用于在打包时 命名APK 生成当前日期字符串
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
android {
//说明要运行该源码,你必选已经安装了android API 23
compileSdkVersion 23
// 说明要运行该源码,你必须已经安装了 android sdk build-tools 23.0.3
buildToolsVersion "23.0.3"
//动态的在build时配置AndroidManifest.xml里的项目
// defaultConfig里的配置可以覆盖manifest里的配置
defaultConfig {
applicationId "test.com.yu"
//表示向下低至android API 14,即androd 4.0和4.0以上的版本都可以运行该工程
minSdkVersion 14
//表示采用的目标android API是 API 23即 android 5.0
targetSdkVersion 23
versionCode 1
versionName "1.0"
//dex突破65535的限制 突破64k需要的设置
//需要compile 'com.android.support:multidex:'
//需要Application中相关配置
multiDexEnabled true
// 默认是umeng的渠道 UMENG_CHANNEL_VALUE需要与AndroidManifest中的对应位置的命名一致
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
}
// java版本
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
//签名文件配置
signingConfigs {
debug {
// No debug config
//storeFile file("C:/Users/Administrator/.android/debug.keystore")
}
release {
storeFile file("../yourapp.keystore")
storePassword "your password"
keyAlias "your alias"
keyPassword "your password"
}
}
//配置如何构建和打包你的App,默认有debug和release两个类型。
// debug类型包含调试时的信息,并且有debug key签名。release默认是不含签名的
buildTypes {
//调试版本
debug {
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
//混淆配置
minifyEnabled false
signingConfig signingConfigs.debug
versionNameSuffix "-debug"
zipAlignEnabled false
shrinkResources false
}
//发布版本
release {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
/*是否开启混淆,新版本是minifyEnabled不再是 runProguard true
* ProGuard的作用是在byte级别对你的app进行分析优化,使得你的App变得更小,更快
* 设置minifyEnabled true,就会在打包的时候进行代码混淆处理.
* 其中proguard-android.txt不用管,在sdk目录里面,主要是配置了proguard.cfg文件。
* 直接在android studio创建项目不会有这个文件,而是 proguard-rules.pro文件
* 其实一样的,因为项目是从eclipse迁移过来的,之前在eclipse上混淆是 proguard.cfg文件*/
minifyEnabled false
/*加载默认混淆配置文件及自定义混淆的配置
* proguardFiles这部分有两段,前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,免去了我们很多事
* 这个文件的目录在 <sdk目录>/tools/proguard/proguard-android.txt
* 后一部分是我们项目里的自定义的混淆文件,目录就在 app/proguard-rules.txt ,
* 如果你用Studio 1.0创建的新项目默认生成的文件名是 proguard-rules.pro
* 这个名字没关系,在这个文件里你可以声明一些第三方依赖的一些混淆规则*/
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//配置发布版本的签名文件
signingConfig signingConfigs.release
zipAlignEnabled true
shrinkResources true // 移除无用的resource文件,这个东西依赖于minifyEnabled,所以minifyEnabled也要为true才行。有可能导致android.content.res.Resources$NotFoundException: File
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// 输出apk名称为boohee_v1.0_2015-01-15_wandoujia.apk
def fileName = "boohee_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}
}
// 友盟多渠道打包
productFlavors {
wandoujia {}
_360 {}
baidu {}
xiaomi {}
tencent {}
taobao {}
}
// 友盟多渠道打包 UMENG_CHANNEL_VALUE需要与AndroidManifest中的对应位置的命名一致
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
//执行lint检查,有任何的错误或者警告提示,都会终止构建,我们可以将其关掉。
lintOptions {
abortOnError false
disable "ignore", "IgnoreResLayout", "InvalidPackage", "MissingRegistered"
}
dexOptions {
preDexLibraries = false //似乎和构建加速有关
//Out of memory 问题 Error:Execution failed for task ':app:dexDebug'. java.lang.OutOfMemoryError: GC overhead limit exceeded
incremental = true;
javaMaxHeapSize "2g"
}
//dex突破65535的限制 突破64k需要的设置 遇到java.lang.NoClassDefFoundError
android.applicationVariants.all { variant ->
task "fix${variant.name.capitalize()}TestMultiDexList" << {
logger.info "Fixing keep manifest for $variant.name"
def keepManifest = new File("$buildDir/intermediates/multi-dex/test/$variant.flavorName/$variant.buildType.name/manifest_keep.txt")
keepManifest.append '\n-keep class *.** { *; }\n'
logger.debug "Updated keepManifest for ${keepManifest.getAbsolutePath()}\n$keepManifest.text"
}
}
tasks.whenTaskAdded { task ->
android.applicationVariants.all { variant ->
if (task.name == "collect${variant.name.capitalize()}TestMultiDexComponents") {
task.finalizedBy "fix${variant.name.capitalize()}TestMultiDexList"
}
}
}
}
//dependencies里指明的就是一些需要用到的第三方库
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:gridlayout-v7:21.0.2'
compile 'com.android.support:appcompat-v7:21.0.2'
compile 'com.android.support:multidex:1.0.0'
compile 'com.google.code.gson:gson:2.3.1'
//ORM 对象型数据库框架 操作本地db
compile 'com.google.code.gson:gson:2.3.1'
//json解析
compile 'com.nineoldandroids:library:2.4.0'
//
compile 'com.android.support:recyclerview-v7:23.0.1'
//RecyclerView引入
/* compile project(path: ':moalib')
//自定义module 包含一些列控件
compile project(path: ':jni')
//本地Jni
compile project(path: ':myjni')//自己定义的jni*/
//aar包:【复制aar到libs】
// 【build.gradle repositories flatDir{ dirs 'libs' }】 【dependencies { compile (name:'XXXXXXX', ext:'aar') }】
compile(name: 'ormlite4.8', ext: 'aar')
compile(name:'jni', ext:'aar')
compile(name:'moalib', ext:'aar')
compile(name:'myjni', ext:'aar')
compile files('libs/conceal_android.jar')
//第三方加密解密
compile files('libs/libconceal.jar')
//第三方加密解密
compile files('libs/umeng-analytics-v5.2.4.jar')
//Umeng分析
}
着谷歌对Eclipse的无情抛弃和对Android Studio的日趋完善,使用gradle构建Android项目已经成为开发者的一项必会良技。那么,问题来了,采用什么样的姿势才能让项目开发构建过程高潮迭起,精彩不断呢?
其实网上有很多关于gradle的文章,gradle官方和谷歌也提供了详细的文档和教程,可素,当你在构建过程中遇到一些问题或者有特殊的爱好(需求)的时候,这些东西未必能帮(mei)上(shen)什(me)么(niao)忙(yong),然后就是一顿翻墙找谷歌蜀黍约约约,去stackoverflow上各种搜刮问大神,最后解决了。即使没有真的解决那么就忍了。
那怎么行?是可忍孰不可忍,奇技淫巧必须有。所以就会有这样一篇文章,我在这里不讲原理,因为我知道很多人明白辣么多的底层原理,仍然撸不上好代码,做不成好项目,出不了好产品,自然也就过不好这一生咯。
我们先从GRADLE构建的时间花销开始谈起。
加速篇
GRADLE的构建过程通常会比较漫长,一个中等项目,10M左右大小的app,一次完整构建大概在5分钟左右,是不是很吓人,当然,如果是在调试阶段,采用Android Studuo 2.0,默认提供的Instant Run方式,每次修改都不会重新构建项目,从而加快了构建过程。恩,这是另一个故事,这里,我们先谈谈GRADLE脚本的加速姿势。
一般来说,GRADLE一次完整的构建过程通常分成三个部分,初始化,配置和执行任务,那么我们可以考虑从这三个部分分别尝试优化。
使用daemon
gradle.properties加入这样一句来开启,如果想让修改全局所有项目都生效,那么修改这个文件~/.gradle/gradle.properties
org.gradle.daemon=true
按需配置
gradle.properties加入这样一句来开启
org.gradle.configureondemand=true
依赖库使用固定版本
项目开发过程中,难免需要用到三方库,这就形成了项目之间的依赖关系,GRADLE提供了多种集成三方库的方式,提供了很方便的项目依赖管理,本地库,库工程,maven库全支持。既然用到库,就会遇到库版本的问题和升级问题,其中maven库的依赖管理支持一种动态版本的方式,也就是说,GRADLE可以做到不依赖具体某个版本的库,而是每次从repo拉取最新的库到本地做编译。具体使用是这样的:
拿gson库举例,如果依赖2.2.1这个版本,可以在build.gradle文件里这样写
dependencies {
compile 'com.google.code.gson:gson:2.2.1'
}
如果不想依赖具体的库,想每次从maven repo中拉取最新的库,那么,可以写成这样:
dependencies {
compile 'com.google.code.gson:gson:2.2.+'
}
也可以写成这样
dependencies {
compile 'com.google.code.gson:gson:2.+'
}
甚至可以这样
dependencies {
compile 'com.google.code.gson:gson:+'
}
其中含义相信不用我解释,大家也看得明白吧。
用”+”来通配一个版本族,这样有个好处是maven上有新库了,不用你操心升级,GRADLE编译的时候自动升级了,但是带来了两个坏处,一是,有可能新版库的接口改了,导致编译失败,这个时候需要修改代码做升级适配;更大的坏处是,每次GRADLE编译完整的项目,都会去maven上试图拉取最新的库,这样,拖慢了编译速度,尤其在网络非常差的时候,所以,为了构建速度,建议写死依赖库的版本号。
升级到最新的GRADLE和JDK
gradle/wrapper/gradle-wrapper.properties并修改distributionUrl=https://services.gradle.org/distributions/gradle-2.11-all.zip到你想升级的版本就可以了。
减少编译脚本中的I/O操作
有时候,编译脚本中会有一些代码做动态信息的获取,比如想从git中获取一个数字作为版本号
def cmd = 'git rev-list HEAD --first-parent --count'
def gitVersion = cmd.execute().text.trim().toInteger()
android {
defaultConfig {
versionCode gitVersion
}
}
其实这个操作主要是为了在构建的机器上为了发布版本而做的,日常环境研发调试无需这样,所以可以优化成如下方式:
def gitVersion() {
if (!System.getenv('CI_BUILD')) {
// don't care
return 1
}
def cmd = 'git rev-list HEAD --first-parent --count'
cmd.execute().text.trim().toInteger()
}
android {
defaultConfig {
versionCode gitVersion()
}
}
并行构建模块化项目
gradle.properties,加入如下行:
org.gradle.parallel=true
基础配置篇
全局基础配置管理
设置全局编码
如果导入一个windows下编写的项目,而代码中有中文注释,采用GBK, GB18030等编码方式时,编译会报错,可以采用如下方式统一项目的编码
allprojects {
repositories {
jcenter()
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
}
设置全局编译器的版本
如果编程过程中采用了新版JDK(比如1.7)才支持的特性(比如new HashMap<>这样的写法),而编译的时候默认是旧版的JDK(比如1.6),这个时候编译会报错,采用如下方式可以指定用哪个版本的编译器编译,前提是JAVA_HOME指定的JDK是大于等于新版JDK的哦^o^,其他和java编译器相关的也可以在这里配置
allprojects {
repositories {
jcenter()
}
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
}
如果不想全局生效,可以将tasks.withType(JavaCompile)放入某个子项目中。
配置签名信息
gradle.properties或者local.properties文件里,这样,gradle脚本可以直接引用,如果是放在一个自定义的文件中,gradle脚本需要提供相应的代码来读取文件的内容。 文件内容参考如下:
RELEASE_KEY_PASSWORD=android
RELEASE_KEY_ALIAS=androidreleasekey
RELEASE_STORE_PASSWORD=android
RELEASE_STORE_FILE=../resources/release.keystore
DEBUG_KEY_PASSWORD=android
DEBUG_KEY_ALIAS=androiddebugkey
DEBUG_STORE_PASSWORD=android
DEBUG_STORE_FILE=../resources/debug.keystore
gradle脚本引用代码参考:
android {
signingConfigs {
debug {
storeFile file(DEBUG_STORE_FILE)
storePassword DEBUG_STORE_PASSWORD
keyAlias DEBUG_KEY_ALIAS
keyPassword DEBUG_KEY_PASSWORD
}
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
}
}
}
gradle.properties或者local.properties文件里,那就需要自己写代码读取咯,假设签名信息放在signing.properties文件中, 文件内容可以参考上面,读取文件的代码放入gradle脚本中就可以了,参考代码如下
def File propFile = new File('signing.properties')
if (propFile.canRead()) {
def Properties props = new Properties()
props.load(new FileInputStream(propFile))
if (props != null && props.containsKey('RELEASE_STORE_FILE') && props.containsKey('RELEASE_STORE_PASSWORD') &&
props.containsKey('RELEASE_KEY_ALIAS') && props.containsKey('RELEASE_KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['RELEASE_STORE_FILE'])
android.signingConfigs.release.storePassword = props['RELEASE_STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['RELEASE_KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['RELEASE_KEY_PASSWORD']
println 'all good to go'
} else {
android.buildTypes.release.signingConfig = null
println 'signing.properties found but some entries are missing'
}
} else {
println 'signing.properties not found'
android.buildTypes.release.signingConfig = null
}
特别注意
如果在构建类型(buildTypes)或者产品种类(productFlavors)需要引用到上述的签名类型,请注意一定要把签名类型的定义放在引用它的对象之前,否则会报错找不到签名配置。其实整个GRADLE语法都是先定义后引用的,这个让写惯JAVA的我确实不习惯。
设置第三方maven地址
其中name和credentials是可选项,视具体情况而定
allprojects {
repositories {
maven {
url 'url'
name 'maven name'
credentials {
username = 'username'
password = 'password'
}
}
}
}
GRADLE脚本拆分以及引用
如果一个gradle脚本太大,可以按照具体任务的类型拆分成几个子脚本,然后引入到主脚本中
apply from:"../resource/config.gradle"
全局变量定义及引用
可以在顶层build.gradle脚本中定义一些全局变量,提供给子脚本引用
ext {
// global variables definition
compileSdkVersion = 'Google Inc.:Google APIs:23'
buildToolsVersion = "23.0.3"
minSdkVersion = 14
targetSdkVersion = 23
}
子脚本引用
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
}
构建参数篇
构建参数设置
AndroidManifest占位符,BuildConfig以及资源配置
根据版本类型和构建变种定义不同的变量值供程序引用
manifestPlaceholders = [APP_KEY:"release"]
buildConfigField "String", "EMAIL", "\"release@android.studio.com\""
resValue "string", "content_main", "Hello world from release!"
buildConfigField支持Java中基本数据类型,如果是字符串,记得加转义后的双引号 resValue支持res/values下的资源定义,字符串无需加转义后的双引号
设置支持的语言
利用这个配置可以去掉三方库中无用的语言(人类的自然语言,非计算机编程语言)
android {
defaultConfig {
resConfigs "zh"
}
}
重命名产出的文件
需要将产出的aar/apk移到另外一个地方的时候
android.libraryVariants.all { variant ->
variant.outputs.each { output ->
if (output.outputFile != null && output.outputFile.name.endsWith('.aar')) {
def name = "${rootDir}/demo/libs/library.aar"
output.outputFile = file(name)
}
}
}
删除unaligned apk
删除无用的apk中间产物
android.libraryVariants.all { variant ->
variant.outputs.each { output ->
if (output.zipAlign != null) {
output.zipAlign.doLast {
output.zipAlign.inputFile.delete()
}
}
}
}
将自定义的任务加入构建流程
有时候编写了一些自定义的任务,希望加入到构建流程中对输入做预处理或者对输出做后处理
project.tasks.whenTaskAdded { task ->
android.applicationVariants.all { variant ->
if (task.name == "prepare${variant.name.capitalize()}Dependencies") {
task.dependsOn ":library:assemble${variant.name.capitalize()}"
}
}
}
比如这里app工程依赖library的构建,可以这样手工指定依赖关系
打包选项
有时候引用的三方库会带有一些配置文件xxxx.properties,或者license信息,打包的时候想去掉这些信息,就可以这样做
android {
packagingOptions {
exclude 'proguard-project.txt'
exclude 'project.properties'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/DEPENDENCIES'
}
}
lint选项开关
lint会按默认选项会做严格检查,遇到包错误会终止构建过程,可以用如下开关关掉这个选项,不过最好是重视下lint的输出,有问题及时修复,避免潜在问题
android {
lintOptions {
abortOnError false
}
}
依赖库篇
三方库(本地,maven)的依赖和工程库依赖关系
依赖库按构建目标定制
不同的依赖库可以按构建目标做定制,比如freemium的变种集成了广告,就可以这样写
dependencies {
freemiumCompile 'com.google.android.gms:ads:7.8.0'
}
aar本地库依赖
jar本地库的依赖很容易写,aar本地库的依赖稍微麻烦些
allprojects {
repositories {
jcenter()
flatDir {
dirs 'libs'
}
}
}
dependencies {
compile(name:'本地库aar的名字,不用加后缀', ext:'aar')
}
NDK篇
NDK配置
只保留某一个abi,比如armeabi
多余的本地库
android {
defaultConfig {
ndk {
abiFilters 'armeabi'
}
}
}
特殊任务篇
本篇主要是讲述一些原本GRADLE默认构建脚本不支持的任务,如果通过自定义task来完成
如何产生Jar文件
classes.jar),我们要做的,其实只要把这个jar文件拷贝出来并且重新命名就可以了。脚本参考如下:
android.libraryVariants.all { variant ->
variant.outputs.each { output ->
def file = output.outputFile
def fileName = 'classes.jar'
def name = variant.buildType.name
task "makeJar${variant.name.capitalize()}" << {
copy {
from("${projectDir}/build/intermediates/bundles/"+"${name}") {
include(fileName)
}
into(file.parent)
rename (fileName, "${project.name}"+"-${name}.jar")
}
}
}
}
project.tasks.whenTaskAdded { task ->
android.libraryVariants.all { variant ->
if (task.name == "bundle${variant.name.capitalize()}") {
task.finalizedBy "makeJar${variant.name.capitalize()}"
}
}
}
这个办法是不是很讨巧,