Android Gradle
官方定义
Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开元工具。使用一种基于Groovy的特定语言来声明项目设置,抛弃了基于XML的各种繁琐配置。
Groovy是用于Java虚拟机的一种敏捷的动态语言。它是一种成熟的面向对象的编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。
为什么要了解命令行编译
在很多情况下,都是使用Android Studio来build、debug项目。Android studio能够满足平时开发的大多数需求,但是某些情况下命令能够让编译的效率更高,过程更加明朗。
Gradle本身并不知道如何编译APK文件,因为Gradle实际上是一个通用的构建工具。它不限于构建Android。在Gradle的GitHub仓库中,被描述为:构建工具。
了解Gradle之后可以做:
- 自定义编译输出文件格式
- hook Android编译过程
- 配置和改善Gradle编译速度
Android工程中的Gradle
工程Project中的build.gradle:工程控制Gradle编译配置
模块module中的build.gradle:控制每个Module的编译过程
gradle.properties:gradle动态参数的配置文件
local.properties:本地的配置 比如:SDK位置
gradle-wrapper.properties:gradle本地代理,声明了指向目录和版本
distributionUrl:指定gradle版本不存在时,就从Value的地址中下载
settings.gradle:配置Gradle中的Module管理
Gradle-Wrapper
gradle wrapper的诞生主要是解决Gradle不断发展的兼容性问题
gradle wrapper包含一些脚本文件和针对不同系统下的运行文件。wrapper有版本区分,但是并不需要手动下载,每当运行脚本的时候,如果本地没有会便会自动下载对应版本。
在不同操作系统下执行的脚本不同,在Mac系统下执行./gradlew…,在windows下执行gradle.bat进行编译。
如果是直接从eclipse中的项目转换过来的,程序并不会自动创建weapper脚本,我们需要手动创建,在命令行输入以下命令:
gradle wrapper --gradle-version 2.4
Gradle basics
Gradle会根据build文件的配置生成不同的task,可以直接单独执行每一个task。通过./gradlew tasks列出所有task。如果通过们同事还想列出每个task对应依赖的其他task,可以使用./gradlew tasks -all。
每当点击build,rebuild,clean菜单的时候,执行的就是一些gradle task
常用Gradle Task
- ~表示gradlew
- gradlew是包装器,自动下载包装里定义好的gradle版本,保证编译环境统一gradle是用本地的gradle
- gradlew task –all:罗列出所有Task,同时携带具体作用和相互关系
- gradlew assembleDebug:导出所有渠道测试包
- ~assembleRelease:导出所有渠道正式包
- ~assembleBaiduDebug –stacktrace:导出指定渠道测试包,同时携带异常信息
- ~–stop:立即停止编译
- ~check:执行lint检测编译
- ~build:执行了check和assemble
- ~clean:清除所有编译输出文件,比如apk
项目自动化
- Gradle是一个构件工具,在开发软件时,需要用IDE来编译。
具有以下优点:
- 可以尽量防止开发动手介入从而节省了开发的时间并减少错误的发生。
- 自动化可以自定义有序的步骤来完成代码的编译、测试和打包等工作,让重复的步骤变得简单。
- IDE可能受到不同操作系统的限制,而自动化构建是不会依赖于特定的操作系统和IDE的,具有平台无关性。
构建工具
用于实现项目自动化,是一种可编程的工具,可以用代码来控制构建流程最终生成可交付的软件。构建工具可以帮助创建一个重复的、可靠的、无需手动介入的、不依赖于特定的操作系统的IDE的。
Gradle构建生命周期
Gradle中有非常重要的两个对象。Project和Task。
每个项目的编译至少有一个Project,一个build.gradle就代表一个project,每个project里包含了多个task,task里又包含了很多action,action是一个代码块,里面包含了需要被执行的代码。
在编译过程中,Gradle会根据build相关文件,聚合所有的project和task,执行中的action。由于build.gradle文件中的task非常多,先执行那个后执行那个需要以一种逻辑来保证。这种逻辑就是依赖逻辑,几乎所有的Task都需要依赖其他task来执行,没有依赖的task会首先被执行。所以到最后所有的Task会构成一个有向无环图的数据结构。
编译过程分为三个阶段:
- 1、初始化:Gradle为每个项目创建一个project实例,在多项目构建中,Gradle会找出哪些项目依赖需要参与到构建中。
- 2、配置:执行所有项目的构建脚本,也就是执行每个项目的build.gradle文件。
- 3、执行:Gradle按照依赖顺序依次执行task。
Gradle优势
- 灵活性:Gradle提供了一系列的API有能力去修改或者定制项目的构建过程。
- 粒度性:源码的编译,资源的编译,都是一个一个Task,可以修改task来达到精细控制。
- 扩展性:Gradle支持插件机制。
- 兼容性:Gradle自身功能强大,而且还兼容所有的Maven、Ant功能。
Extension
扩展,作用是通过实现自定义的Extension可以在Gradle脚本中增加类似于android这样命名空间配置
- publicType:创建Extension实例暴露数来的类型
- name:要创建Extension的名字,可以为任意字符串,不能与已有的重复,否则会报异常
- instanceType:该Extension的类型
- constructionArguments:类的构造函数参数值
- 创建Extension对象都默认实现了ExtensionAware接口
NamedDomainObjectContainer
命名领域对象容器, 它能够通过DSL创建指定的type对象实例;指定的type必须有一个public构造函数,且必须带有一个String name的参数,type类型的领域对象必须有名为“name”的属性;实现了SortedSet接口的容器,所以所有领域对象的name属性值都必须是唯一的,在容器中内部都是用name属性来排序
Gradle提供的生命周期回调:
- 在project进行配置前调用,child project必须在root project中设置才能生效root project必须在setting.gradle中设置才会生效:
void beforeProject(Closure closure) - 在project配置后调用:
afterProject(Closure closure) - 构建开始前调用:
void buildStarted(Closure closure) - 构建结束后调用:
void buildFinished(Closure closure) - 所有project配置完成后调用:
void projectsEvaluated(Closure closure) - 当settings.gradle中引入所有project 都被创建好后调用,只在该文件设置才会生效:
void projectsLoaded(Closure closure) - setting.gradle配置完成后调用,只对settings.gradle设置生效:
void settingsEvaluated(Closure closure)
生命周期回调的执行顺序:
- gradle.settingsEvaluated->
- gradle.projectsLoaded->
- gradle.beforeProject->
- project.beforeEvaluate->
- gradle.afterProject->
- project.afterEvaluate->
- gradle.projectsEvaluated->
- gradle.taskGraph.graphPopulated->
- gradle.taskGraph.whenReady->
- gradle.buildFinished
resolutionStrategy
解析策略,可以在gradle解析各种依赖库时,配置一些解析策略。
dependencies
属于gradle的依赖配置,定义了当前项目需要依赖其他库。
-
本地module依赖
dependencies{ implementation project(':XX') ....... }
-
本地库依赖,可以通过**fileTree()**方法添加一个文件夹
implementation fileTree(dir: 'libs', include: ['*.jar'])
-
远程依赖
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
-
依赖配置
- implementation:只有直接依赖的module可以访问,大部分app module和test module 建议使用这个
- api:可以依赖传递,编译的时候所有可以访问该依赖的模块都会被编译,增加太多时间,建议优先使用implementation
- compileOnly:只在编译的时候有效,不参与打包
- runtimeOnly:只在打包的时候有效,不参与编译
- annotationProcessor:添加一个注释处理器的库使用,将编译路径和注释处理器路径分开
-
在引用库的时候,每个库名称包含三个元素:组名、库名、版本号:
dependencies{ compile 'com.google.code.gson:gson:2.3' compile 'com.squareup.retrofit:retrofit:1.9.0' }
-
如果要保证依赖的库始终处于最新状态,可以通过添加通配符的方式:
dependencies{ compile 'com.android.support:support-v4:22.2.+' //告诉gradle,得到的最新生产版本 compile 'com.android.support:appcompat-v7:22.2+' //告诉gradle,想得到最新minor版本,并且最小的版本号是2 compile 'com.android.support:recyclerview-v7:+' //告诉gradle,得到最新的library }
-
配置本地.so库。在配置文件中做下面的配置,然后在对应的位置建立文件夹,加入对应平台的.so文件
android{ sourceSets.main{ jniLibs.srcDir 'src/main/libs' } }
apply plugin
作为Android的应用程序,这一步是必须的,plugin中提供了Android编译、测试、打包等 所有task。
-
apply plugin: 'com.android.application’实际对应的原型是:com.android.build.gradle.AppExtension,表示此项目模块类型为Android App Module,对应构建生成的文件为.apk类型文件。这代表当前的modle是一个app,而不是一个library
-
apply plugin: ‘com.android.library’
实际对应的原型是:com.android.build.gradle.LibraryExtension,表示此项目模块类型为Android Library Module,对应构建生成的文件为.arr类型的文件。 -
apply plugin: ‘com.android.test’
实际对应的原型是:com.android.build.gradle.TestExtension,表示此项目模块类型为Android test Module,可以在单个模块内通过targetProjectPath指定项目,用于对应项目的单元测试。 -
apply plugin: ‘com.android.feature’
实际对应的原型是:com.android.build.gradle.FeatureExtension,表示此项目模块类型为Android feature Module,主要用于单个模块内实现特性,以支持Android Instant Apps。 -
arr文件:包含所有资源,class以及res资源文件全部。如果想分享一个library,该
-
使用了Android api,或者包含了Android资源文件,那么arr文件是适合的。依赖库和应用工程一样,可以使用相同的tasks来构建和测试依赖工程,也可以有不同的构建版本。
-
jar文件:只包含了class文件与清单文件,不包含资源文件。
-
依赖包:作为gradle默认的属性之一,为了所开发的app定义了所有的依赖包。默认情况下,依赖了所有在libs文件下的jar文件,同时包含了AppCompat这个文件。每一个构建版本都有自己的依赖包,gradle自动为每一个构建的版本创建不同的依赖配置。如果想为debug版本添加一个logging框架,可以这么做:
dependencies{ compile fileTree(dir: 'libs',include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.2.0' debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3' }
Gradle中android下的定义
是编译文件中最大的代码块,关于android的所有配置都在这。
-
compileSdkVersion**: **编译SDK的版本
-
buildToolsVersion**: **构建工具的版本
-
defaultConfig:是程序的默认配置,如果在AndroidManifest.xml里面定义了与这里相同的属性,会以这里为主。
- applicationId:应用包名,app的唯一标识
- minSdkVersion:项目最低兼容的Android版本
- targetSdkVersion:项目的目标版本
- versionCode:项目版本号
- versionName:项目版本名
- testInstrumentationRunner:把AndroidJunitRunner 设置成默认的test instrumentationrunner
- ndk:Native Development Kit,是Android的一个工具开发包
- NDK是属于Android的,与Java没有直接关系
- 作用是快速开发C、C++的动态库,并自动将so和应用一起打包给APK
- 可以通过NDK在Android中使用JNI与本地代码交互
- JNI:Java Native Interface,即本地接口
- 作用是使Java与本地其他类型语言交互
- 在Java代码里调用C、C++语言的代码,或者C、C++代码调用Java代码
- & JNI是Java调用Native语言的一种特征;JNI是属于Java的,与Android没有直接关系
-
budilTypes
- release
- minifyEnable false:是否对项目的代码进行混淆
- proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’:
指定混淆时使用的规则文件
proguard-android.txt是在Android SDK目录下,里面是所有项目通用的混淆规律;
proguard-rules.pro是在当前项目的根目录下,里面可以没编写当前项目特有的混淆规则。
- release
-
SourceSets:被称作为原集合,一般可以用它来指定资源的路径。
- srcDirs:存在的java代码路径
- inclides:针对“srcDirs”文件夹路径,设置将哪些类进行编译打包
- excludes:针对“srcDirs”文件夹路径,设置将哪些类不进行编译打包
- assete:设置assets的存放位置
- 使用方法:
assets.srcDir 'src/main/assets','src/main/zinAssets'
- 使用方法:
- aidl:设置aidl的存放位置
- 使用方法:
aidl.srcDir 'src/main/aidl','src/main/zinAidl'
- 使用方法:
- jni:设置jni的存放位置
- 使用方法:
jni.srcDirs '目录1','目录2'
- 使用方法:
- jniLibs:设置jniLibs的存放位置
- 使用方法:
jniLibs.srcDirs '目录1','目录2'
- 使用方法:
- manifest:设置manifest的存放位置
- 使用方法:
mainfest.srcFile 'src/main/ZincManifest.xml'
- 使用方法:
- renderscript:设置renderscript的存放位置
- 使用方法:
renderscript.srcDirs 'xxx','xxx'
- 使用方法:
- res:设置res的存放位置
- 使用方法:
res.srcDirs 'src/main/res', 'src/main/zinc-res'
- 使用方法:
- resources:设置resources的存放位置
- 使用方法:
resources.srcDir 'xxx','xxx'
- 使用方法:
-
repositories,代码仓库,Gradle支持的三种类型的仓库:Maven,Ivy和一些静态文件或者文件夹。在编译阶段,gradle将会从仓库中取出对应的依赖文件,gradle也有自己的缓存,不会每次都去取这些依赖。
gradle支持多种Maven仓库,一般需要手动加入仓库链接:repositories{ maven{ url "http://repo.acmecorp.com/maven2" } }
如果仓库有密码,也可以同时传入用户名和密码
repositories{
maven{
url "http://repo.acmecorp.com/maven2"
credentials{
username 'user'
password 'secretpassowrd'
}
}
}
也可以使用相对路径配置本地仓库,可以通过配置项目中存在的静态文件夹,作为本地仓库:
repositories{
flatDir{
dirs 'aars'
}
}
History
默认情况下,本地历史记录配置为存储最近5个工作日的修订。
一个Android应用从开始编译到打包,大致分经历三个步骤:编译前检查 --> 编译(资源文件和源码) --> 打包签名
Gradle Files
对于一个gradle项目,最基础文件配置如下:
MyApp
build.gradle
settings.gradle
app
build.gradle
一个项目有一个setting.gradle、包括一个顶层的build.gradle文件、每个Module都有自己的一个build.gradle文件。
-
setting.gradle:这个setting文件定义了对应的module应该加入到编译过程,对于单个module的项目可以不需要这个文件,但是对于multimodule的项目就需要这个文件,否则gradle不知道需要加载那些项目。这个文件会在初始化阶段被执行。
-
顶层的build.gradle:顶层的build.gradle文件的配置最终会被应用到所有的项目中。典型配置如下:
buildscript{ repositories{ jcenter() } dependencies{ classpath 'com.android.tiils.build:gradle:1.2.3' } } allprojects{ repositories{ jcenter() } }
-
buildscript:定义了Android编译工具的类路径。repositories中,jCenter()是一个著名的Maven仓库。
-
allprojects:中定义的属性会被应用到所有moudle中,为了保证每个项目的独立性,一般不会在这恶里面操作太多共有的东西
-
每个项目单独的build.gradle:针对每个moudle的配置,如果这里定义的选项和顶层build.gradle定义相同,后者会被覆盖。典型的配置如下:
apply plugin: 'com.android.application' android{ compileSdkVersion 22 buildtoolsVersion "22.0.1" defaultConfig{ applicationId "com.gradleforandroid.gettingstarted" minSdkVersion 14 argetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes{ release{ minifyEnabled false proguardFiles getDefauleProguardFile ('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies{ compile fileTree(dir: 'libs',include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.2.0' }
-
buildTypes:应以了编译类型,针对每个类型可以有不同的编辑配置,不同的编译命令。默认的有debug、release的类型。
BuildConfig
通过BuildConfig.DEBUG来判断当前的版本是否是debug版本,如果是就输出一些只能在debug环境下才会执行的操作。这个类就是由gradle根据配置文件生成的。
这个功能非常强大,可以通过在这里设置一些key-value对,这些key-value对在不同的编译类型的apk下的值不同,可以用debug和release两种环境定义不同的服务器。比如:
android{
buildTyoes{
debug{
buildConfigField "String", "API_URL",
"\"http://test.example.com/api\""
buildConfigField "boolean", "LOG_HTTP_CALLS", "true"
}
release{
buildConfigField "String", "API_URL",
"\"http://test.example.com/api\""
buildConfigField "boolean", "LOG_HTTP_CALLS", "false"
}
}
}
除此之外,还可以为不同的编译类型设置不同 的资源文件,比如:
android{
buildTypess{
debug{
resValue "String", "app_name", "Example DEBUG"
}
release{
resValue "String", "app_name", "Example"
}
}
}
Build Variants(发布)
在开发中可能会遇到这样的需求:
- 需要在debug和release两种情况下配置不同的服务器地址
- 当打市场渠道包的时候,可能需要打免费版、收费版,或者内部版、外部版的程序
- 渠道首发包通常需要在欢迎页添加渠道的logo,等
- 为了让市场版和debug版本同时存在于一个手机,需要编译的时候自动给debug版本不一样的包名
这些需求要在编译的时候动态根据当前的编译类型输出不同的apk文件,这时候就需要使用到buildType了
Build Type
android默认带有Debug和Release两种编译类型,比如现在有一个新的statging的编译类型
android{
buildTypes{
staging.initWith(buildTypes.debug)
staging{
applicationIdsuffix ".staging"
versionNameSuffix "-staging"
debuggable = false
}
}
}
Source sets
每当创建一个新的build tyoe的时候,gradle默认都会创建一个新的source set。可以创建与main文件夹同级的文件夹,根据类型的不同可以选择对某些源码直接进行替换。
除了代码可以替换,资源文件也可以替换
不同编译类型的项目,依赖可以不同。如果需要在staging和debug两个版本中使用不同的log框架,可以这样配置:
dependencies{
compile fileTree(dir: 'libs', include: ['*jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3'
}
Product flavors
当需要针对同一份源码编译不同的程序(包名也不同),比如免费版和收费版,就需要Product flavors。
注:Product flavors和Build Type是不一样的,而且属性也不养,所有的product flavor版本和defaultConfig共享所有属性。*
更复杂的情况下,可以通过多个product的维度进行组合,比如color和price两个维度去构建程序。这时候就需要用到flavorDimensions:
android{
flavorDimensions "color", "price"
porductFlavors{
red{
flavorDDimension "color"
}
blue{
flavorDDimension "color"
}
free{
flavorDDimension "price"
}
paid{
flavorDDimension "price"
}
}
}
根据这些配置,再次查看便会发现多了这些task:
- blueFreeDebug and blueFreeRelease
- bluePaidDebug and bluePaidRelease
- redFreeDebug and redFreeRelease
- redPaidDebug and redPaidRelease
Resource merage priority
Build type ---> Flavor ---> Main ---> Dependencies
在Build Type中,定义的资源优先级最大,在Library中定义的资源优先级最低。
Signing configurations
在打包市场版本时,需要输入keystore数据;如果是debug版本,系统会默认帮助配置这些信息。这些信息在gradle中都配置在signingConfigs中。
android{
signingConfigs{
staging.initWith(signingConfigs.debug)
release{
storeFile file("release.keystore")
storePassword"secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
}
配置之后需要在build type中直接使用
android{
buildTyoes{
release{
signingConfig signingConfig.release
}
}
}
Optimize(优化)
Speeding up multimodule builds
可以通过一下方式加快gradle的编译:
-
开启并行编译,在项目根目录下面的gradle.properties中设置
org.gradle.parallel=true
-
开启编译守护进程,该进程在第一次启动后一直存在,当进行第二次编译的时候,可以重新使用该进程。同样是gradle.properties中设置。
org.gradle.daemon=true
-
加大可用编译内存:
org.gradle.jvmargs=-Xms256m -Xmx1024m
Reducing apk file
在编译的过程中,可能会有很多资源没有被用到,此时就可以通过shrinkResources来优化资源文件,除去哪些不必要的资源。
android{
buildType{
release{
minifyEnabled=true
shrinkResources=true
}
}
}
当需要查看该命令帮助减少了多少无用的资源时,可以通过运行shrinkReleaseResources命令来查看log。
某些情况下,一些资源是需要通过动态加载的方式载入的,这时候需要像Progard一样对资源进行keep操作。方法就是在res/raw/下建立一个keep.xml文件
:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB
to 354KB: Removed 18%
Manual shrinking
当可能有非常多的国际化资源,如果场景里只用到了English,Danish,Dutch的资源,可以指定resConfig:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/keep_me.@layout/also_used_*"/>
对于尺寸也可以这样做
android{
defaultConfig{
resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
}
Profiling(测评)
当执行完所有task的时候,可以通过添加profile参数生成一份执行报告在reports/profile中。在开发的过程中,可能会遇到很多情况需要自己能够自定义task。
Groovy语法
变量
在groovy中,没有固定的类型,变量可以通过def关键字引用:
def name = 'Andy'
可以通过一些简单引号引用一串字符的时候,这个字符串只能是单纯的字符串,如果使用了双引号,在字符串里面还支持插值操作:
def name = 'Andy'
def greetinf = "Hello, $name!"
循环
for(int i in i..10) {//用for in遍历1到10(包含头尾)
println i
}
def dd = [1,2,3,4,5] //for in 遍历数组
for(d in dd){
println d
}
def mp = ["key":21,"ju":12,"aaaaa":223]//for in 遍历 map
for(d in map){
println d
}
另外,如果简单的多次执行同一段代码,还可以这样写(设计闭包):
10.times{
variable -> println variable
}
groovy的IO读写操作
new File("test.txt").eachLine { line -> println "line $line" } //读取每一行
//读取文件全部内容
File f = new File("test.txt")
println "文件全部内容 \n$f.text"
//把helloWorld字符串写入到test.txt文件
new File("test.txt").withWriter('utf-8', {
weiter -> writer.writeLine("hello world")
})
方法
类似python一样,通过def关键字定义一个方法,如果这个方法不指定返回值,默认返回最后一行代码的值。
def square(def num){
num * num
}
square 4
类
Groovy也是通过Groovy定义一个类:
class MyGroovyClass{
String greeting
String getGreeting(){
return 'Hello!'
}
}
-
在Groovy中,默认所有得类和方法都是public的,所有类的字段都是private的
-
和java一样,可以通过new关键字得到类的实例,使用def接受对象的引用:def instance = new MyGroovyClass()
-
在类中声明的字段都默认会生成对应的setter,getter方法。注:groovy的方法调用可以没有括号的,而且也不需要分号结尾。除此之外甚至也可以直接调用
-
可以直接通过instance.greeting这样的方式拿到字段值,但其实这也会通过其get方法,而且不是直接拿到这个值。
###map、collections
在Groovy中,定义一个列表是这样的:List list = [1,2,3,4,5]
遍历一个列表是这样的:list.each() { element ->
printle element
}
定义一个map是这样的:Map pizzaPruces = [margherita:10,pepperoni:12]
获取一个map值是这样的:pizzaPrices.get(‘pepperoni’)
pizzaPrices[‘pepperoni’]
闭包
在Groovy中有一个闭包的概念。闭包可以理解为就是Java中的匿名内部类。闭包支持类似lamda形式的语法调用。
def square = { num ->
num * num
}
square 8
如果只有一个参数,可以省略这个三处,默认使用it作为参数:
Closuresquare = {
it * it
}
square 16
Groovy in Gradle
-
apply
apply plugin: 'com.android.application'
这段代码调用了project对象的apply方法,传入了一个以plugin为key的map。完整写出来是这样的:
project.apply([plugin:'com.android.application'])
-
dependencies
dependencies{ compile 'com.google.code.gson:gson:2.3' }
实际调用的时候会传入一个DependencyHandler的闭包,
projcet.dependenceies({
add('compile', 'com.google.code.gson:gson:2.3',{
//Configuration statements
})
})
Task
-
创建一个task
task hello{ println 'Hello, world!' }
运行该task
./gradlew hello
注意:当task被执行的时候,所有的action都会被依次执行。如果要加入自己的action,可以通过复写doFirst()和doLast()方法。
task hello{
println 'Configuration’
doLast{
println 'Goodbye'
}
doFirst{
println 'Hello'
}
}
打印出来是这样的
$ gradlew hello
Configuration
:hello
Hello
Goodbye
-
Task依赖:task之间的关系就是依赖关系,关于Task的依赖有两种,must RunAfter和dependsOn。
task task1 << { printfln 'task1' } task task2 << { printfln 'task2' } task2.mustRunAfter task1
和
task task1 << {
printfln 'task1'
}
task task2 << {
printfln 'task2'
}
task2.dependsOn task1
区别就是,运行的时候前者必须要都按顺序加入gradlew task2 task1才可以顺利执行,否则单独执行每个任务,后者只需要执行gradlew task2即可同时执行两个任务。
practice
可以通过两个例子来时间task
keystor保护
android{
signingConfigs{
staging.initWith(signingConfigs.debug)
release{
storeFile file("release.keystore")
storePassword"secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
}
直接将store的密码文件写在了这里对于产品的安全性来说不太好,特别是如果该源码开源,别人就可以用你的id去发布app。对于这种情况,需要构建一个动态加载任务,在编译release源码的时候从本地文件获取keystore信息:
task getReleasePassword << {
def password = ''
if(rootProject.file('private.properties').exists()){
Properties properties = new Properties();
properties.load(rootProject.file
('private.properties').newDataInputStream())
password = properties.getProperty('release.password')
}
}
还可以设置一个保险措施,万一没有找到对应的文件,需要用户从控制台输入密码:
if(!password?.trim()){
password = new String(System.console().readPassword
("\nWhat's the secret password? "))
最后设置最终值
然后设置release任务依赖于刚刚设置的任务
tasks.whenTaskAdded{ theTask ->
if (theTask.name.equals("packageRelease")) {
theTask.dependsOn "getReleasePassword"
}
}
通过hook Android编译插件 重命名apk
android.applicationVariants.all{ variant ->
variant.outputs.each{ output ->
def file = output.outputFile
output.outputFile = new File(file.parent,
file.name.replace(".apk", "-${variant.versionName}.apk"))
}
}
最后编译出来的apk名字类似app-debug-1.0.apk。
Gradle优化
主要分为两大方面:
- 包大小优化
- 编译速度优化
包大小优化
- 去除不必要的资源
- 把图片转成webp格式
- 去除无需兼容的ndk包
编译优化速度
- 开启并行编译
- 开启编译守护进程
- 加大可编译内存
- ZipAlign优化
- 配置dexOptions和开启library pre-dexing(dex预处理)
- 构建一个变体
- 用静态的版本依赖
- 分多module管理
- 构建分析
debug和release的区别
debug版本
debug是调试的意思,Debug 版本就是为调试而生的,编译器在生成 Debug 版本的程序时会加入调试辅助信息,并且很少会进行优化。
release是发行的意思,release 版本就是最终交给用户的程序,编译器会使尽浑身解数对它进行优化,以提高执行效率,虽然最终的运行结果仍然是我们期望的,但底层的执行流程可能已经改变了。编译器还会尽量降低 Release 版本的体积,把没用的数据一律剔除,包括调试信息。