Gradle 是 Android 如今主流的编译工具,虽然在Gradle 出现以前和以后都有对应更快的编译工具出现,可是 Gradle 的优点就在于它是亲儿子,Gradle 确实比较慢,这和它的编译过程有关,可是如今的Gradle 编译速度已经有了成倍提升。除此以外,相对其余编译工具,最重要的,他和 Android Studio 的关系很是紧密,能够说对于一些简单的程序咱们几乎不须要任何代码上的配置只使用 Android Studio 就能够完成编译和运行。javascript
可是对于一些比较复杂的,特别是多人团队合做的项目咱们会须要一些个性化的配置来提升咱们的开发效率。好比咱们要自定义编译出的apk包的名字、对于一些特殊产品咱们可能会要用同一个项目编译出免费版
和付费版
的apk。这些高级的功能都须要咱们对配置代码进行自定义地修改。php
最近伴随着 Android Studio2.0的发布, Gradle 也进行了一次很是大的升级,叫Instant Run.它的编译速度网上有人用逆天两个字来形容。当咱们第一次点击run、debug按钮的时候,它运行时间和咱们往常同样。可是接下去的时间里,你每次修改代码后点击run、debug按钮,对应的改变将迅速的部署到你正在运行的程序上,传说速度快到你都来不及把注意力集中到手机屏幕上,它就已经作好相应的更改。可是刚出来的彷佛对一些项目的兼容性不太好,如今升级后不知道怎么样。css
在不少状况下咱们都是使用的 Android Studio 来build、debug项目。Android Studio 能知足咱们开发的大多数需求,可是某些状况下命令行可以让咱们编译的效率更高,过程更明朗,一些高级的配置也须要熟悉命令行才可以使用,好比在服务器编译,某些项目初始化的时候若是直接交给Android Studio ,它会一直Loading,你都不知道它在干吗,可是用命令行你就知道它卡在了哪一个环节,你只须要修改某些代码,立刻就可以编译过了。html
we can do everything what we want.java
Gralde Overviewpython
咱们知道,Android 的编译过程很是复杂:android
咱们须要一种工具帮咱们更快更方便更简洁地完成 Android 程序的编译。如今结合Android Studio 咱们通常使用的工具都是Gradle, 在 Gradle 出现之前Android 也有对应的编译工具叫 Ant,在Gradle 出现以后,也有新的编译工具出现,就是FaceBook 的Buck工具。这些编译工具在出现的时候几乎都比 Gradle 要快,Gradle 之因此慢是跟它的编译周期有很大关系。nginx
在解析 Gradle 的编译过程以前咱们须要理解在 Gradle 中很是重要的两个对象。Project和Task。git
每一个项目的编译至少有一个 Project,一个 build.gradle
就表明一个project
,每一个project
里面包含了多个task
,task 里面又包含不少action
,action
是一个代码块,里面包含了须要被执行的代码。apache
在编译过程当中, Gradle 会根据 build 相关文件,聚合全部的project
和task
,执行task 中的 action。由于 build.gradle
文件中的task
很是多,先执行哪一个后执行那个须要一种逻辑来保证。这种逻辑就是依赖逻辑,几乎全部的Task 都须要依赖其余 task 来执行,没有被依赖的task 会首先被执行。因此到最后全部的 Task 会构成一个 有向无环图(DAG Directed Acyclic Graph)的数据结构。
编译过程分为三个阶段:
谢绝转载,非要转载,请注明出处http://www.jianshu.com/p/9df3c3b6067a
刚刚咱们提到Gradle 编译的时候的一些相关文件,下面咱们挨个解析一下这些文件。
对于一个gradle 项目,最基础的文件配置以下:
一个项目有一个setting.gradle
、包括一个顶层的 build.gradle
文件、每一个Module 都有本身的一个build.gradle
文件。
build.gradle
文件的配置最终会被应用到全部项目中。它典型的配置以下:buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3' } } allprojects{ repositories{ jcenter() } }
repositories
中,jCenter
是一个著名的 Maven 仓库。allprojects:中定义的属性会被应用到全部 moudle 中,可是为了保证每一个项目的独立性,咱们通常不会在这里面操做太多共有的东西。
每一个项目单独的 build.gradle:针对每一个moudle 的配置,若是这里的定义的选项和顶层build.gradle
定义的相同,后者会被覆盖。典型的 配置内容以下:
plugin
中提供了Android 编译、测试、打包等等的全部task。defaultConfig
就是程序的默认配置,注意,若是在AndroidMainfest.xml
里面定义了与这里相同的属性,会以这里的为主。applicationId
的选项:在咱们曾经定义的AndroidManifest.xml
中,那里定义的包名有两个用途:一个是做为程序的惟一识别ID,防止在同一手机装两个同样的程序;另外一个就是做为咱们R
资源类的包名。在之前咱们修改这个ID会致使全部用引用R资源类的地方都要修改。可是如今咱们若是修改applicationId
只会修改当前程序的ID,而不会去修改源码中资源文件的引用。Gradle 不断的在发展,新的版本不免会对以往的项目有一些向后兼容性的问题,这个时候,gradle wrapper
就应运而生了。
gradlw wrapper 包含一些脚本文件和针对不一样系统下面的运行文件。wrapper 有版本区分,可是并不须要你手动去下载,当你运行脚本的时候,若是本地没有会自动下载对应版本文件。
在不一样操做系统下面执行的脚本不一样,在 Mac 系统下执行./gradlew ...
,在windows 下执行gradle.bat
进行编译。
若是你是直接从eclipse 中的项目转换过来的,程序并不会自动建立wrapper
脚本,咱们须要手动建立。在命令行输入如下命令便可
gradle wrapper --gradle-version 2.4
它会建立以下目录结构:
wrapper 就是咱们使用命令行编译的开始。下面咱们看看 wrapper 有什么样的做用。
Gradle 会根据build 文件的配置生成不一样的task,咱们能够直接单独执行每个task。经过./gradlew tasks
列出全部task。若是经过同时还想列出每一个task 对应依赖的其余task,可使用./gradlew tasks -all
。
其实每当咱们在Android Studio点击 build,rebuild,clean菜单的时候,执行的就是一些gradle task.
有四个基本的 task, Android 继承他们分别进行了本身的实现:
lint
检测编译。assemble
和check
命令这些都是基本的命令,在实际项目中会根据不一样的配置,会对这些task 设置不一样的依赖。好比 默认的 assmeble 会依赖 assembleDebug 和assembleRelease,若是直接执行assmeble
,最后会编译debug,和release 的全部版本出来。若是咱们只须要编译debug 版本,咱们能够运行assembleDebug
。
除此以外还有一些经常使用的新增的其余命令,好比 install命令,会将编译后的apk 安装到链接的设备。
咱们运行的许多命令除了会输出到命令行,还会在build
文件夹下生产一份运行报告。好比check
命令会生成lint-results.html.
在build/outputs
中。
Configuration
这个类相信你们都不会陌生,咱们最经常使用的用法就是经过BuildConfig.DEBUG
来判断当前的版本是不是debug版本,若是是就会输出一些只有在 debug 环境下才会执行的操做。 这个类就是由gradle 根据 配置文件生成的。为何gradle 能够直接生成一个Java 字节码类,这就得益于咱们的 gradle 的编写语言是Groovy, Groovy 是一种 JVM 语言,JVM 语言的特征就是,虽然编写的语法不同,可是他们最终都会编程 JVM 字节码文件。同是JVM 语言的还有 Scala,Kotlin 等等。
这个功能很是强大,咱们能够经过在这里设置一些key-value对,这些key-value 对在不一样编译类型的 apk 下的值不一样,好比咱们能够为debug 和release 两种环境定义不一样的服务器。好比:
除此以外,咱们还能够为不一样的编译类型的设置不一样的资源文件,好比:
Repositories 就是代码仓库,这个相信你们都知道,咱们平时的添加的一些 dependency 就是从这里下载的,Gradle 支持三种类型的仓库:Maven,Ivy和一些静态文件或者文件夹。在编译的执行阶段,gradle 将会从仓库中取出对应须要的依赖文件,固然,gradle 本地也会有本身的缓存,不会每次都去取这些依赖。
gradle 支持多种 Maven 仓库,通常咱们就是用共有的jCenter
就能够了。
有一些项目,多是一些公司私有的仓库中的,这时候咱们须要手动加入仓库链接:
若是仓库有密码,也能够同时传入用户名和密码
咱们也可使用相对路径配置本地仓库,咱们能够经过配置项目中存在的静态文件夹做为本地仓库:
咱们在引用库的时候,每一个库名称包含三个元素:组名:库名称:版本号
,以下:
若是咱们要保证咱们依赖的库始终处于最新状态,咱们能够经过添加通配符的方式,好比:
可是咱们通常不要这么作,这样作除了每次编译都要去作网络请求查看是否有新版本致使编译过慢外,最大的弊病在于咱们使用过的版本很很困难是测试版,性能得不到保证,因此,在咱们引用库的时候必定要指名依赖版本。
File dependencies
经过files()
方法能够添加文件依赖,若是有不少jar文件,咱们也能够经过fileTree()
方法添加一个文件夹,除此以外,咱们还能够经过通配符的方式添加,以下:
Native libraries
配置本地 .so
库。在配置文件中作以下配置,而后在对应位置创建文件夹,加入对应平台的.so
文件。
文件结构以下:
Library projects
若是咱们要写一个library项目让其余的项目引用,咱们的bubild.gradle的plugin 就不能是andrid plugin了,须要引用以下plugin
apply plugin: 'com.android.library'
引用的时候在setting文件中include
便可。
若是咱们不方便直接引用项目,须要经过文件的形式引用,咱们也能够将项目打包成aar
文件,注意,这种状况下,咱们在项目下面新建arrs
文件夹,并在build.gradle 文件中配置 仓库:
当须要引用里面的某个项目时,经过以下方式引用:
在开发中咱们可能会有这样的需求:
这些需求都须要在编译的时候动态根据当前的编译类型输出不一样样式的apk文件。这时候就是咱们的buildType
大展身手的时候了。
android 默认的带有Debug和Release两种编译类型。好比咱们如今有一个新的statging
的编译类型
每当建立一个新的build type 的时候,gradle 默认都会建立一个新的source set。咱们能够创建与main
文件夹同级的文件夹,根据编译类型的不一样咱们能够选择对某些源码直接进行替换。
除了代码能够替换,咱们的资源文件也能够替换
除此以外,不一样编译类型的项目,咱们的依赖均可以不一样,好比,若是我须要在staging和debug两个版本中使用不一样的log框架,咱们这样配置:
前面咱们都是针对同一份源码编译同一个程序的不一样类型,若是咱们须要针对同一份源码编译不一样的程序(包名也不一样),好比 免费版和收费版。咱们就须要Product flavors
。
注意,Product flavors和Build Type是不同的,并且他们的属性也不同。全部的 product flavor 版本和defaultConfig 共享全部属性!
像Build type 同样,product flavor 也能够有本身的source set
文件夹。除此以外,product flavor 和 build type 能够结合,他们的文件夹里面的文件优先级甚至高于 单独的built type 和product flavor 文件夹的优先级。若是你想对于 blue类型的release 版本有不一样的图标,咱们能够创建一个文件夹叫blueRelease
,注意,这个顺序不能错,必定是 flavor+buildType 的形式。
更复杂的状况下,咱们可能须要多个product 的维度进行组合,好比我想要 color 和 price 两个维度去构建程序。这时候咱们就须要使用flavorDimensions
:
根据咱们的配置,再次查看咱们的task,发现多了这些task:
在Build Type中定义的资源优先级最大,在Library 中定义的资源优先级最低。
若是咱们打包市场版的时候,咱们须要输入咱们的keystore数据。若是是debug 版本,系统默认会帮咱们配置这些信息。这些信息在gradle 中都配置在signingConfigs
中。
配置以后咱们须要在build type中直接使用
能够经过如下方式加快gradle 的编译:
gradle.properties
中设置org.gradle.parallel=true
gradle.properties
中设置。org.gradle.daemon=true
org.gradle.jvmargs=-Xms256m -Xmx1024m
在编译的时候,咱们可能会有不少资源并无用到,此时就能够经过shrinkResources
来优化咱们的资源文件,除去那些没必要要的资源。
若是咱们须要查看该命令帮咱们减小了多少无用的资源,咱们也能够经过运行shrinkReleaseResources
命令来查看log.
某些状况下,一些资源是须要经过动态加载的方式载入的,这时候我也须要像 Progard 同样对咱们的资源进行keep操做。方法就是在res/raw/
下创建一个keep.xml
文件,经过以下方式 keep 资源:
对一些特殊的文件或者文件夹,好比 国际化的资源文件、屏幕适配资源,若是咱们已经肯定了某种型号,而不须要从新适配,咱们能够直接去掉不可能会被适配的资源。这在为厂商适配机型定制app的时候是很用的。作法以下:
好比咱们可能有很是多的国际化的资源,若是咱们应用场景只用到了English,Danish,Dutch的资源,咱们能够直接指定咱们的resConfig
:
对于尺寸文件咱们也能够这样作
当咱们执行全部task的时候咱们均可以经过添加--profile
参数生成一份执行报告在reports/profile
中。示例以下:
咱们能够经过这份报告看出哪一个项目耗费的时间最多,哪一个环节耗费的时间最多。
Practice
在开发的过程当中,咱们可能会遇到不少状况须要咱们可以本身定义task,在自定义task 以前,咱们先简单看看groovy 的语法。
咱们前面看到的那些build.gradle 配置文件,和xml 等的配置文件不一样,这些文件能够说就是能够执行的代码,只是他们的结构看起来通俗易懂,和配置文件没什么两样,这也是Google 之因此选择Groovy 的缘由。除此以外,Groovy 是一门JVM 语言,也就是,Groovy 的代码最终也会被编译成JVM 字节码,交给虚拟机去执行,咱们也能够直接反编译这些字节码文件。
咱们这里简单地说一下 groovy 一些语法。
在groovy 中,没有固定的类型,变量能够经过def
关键字引用,好比:
def name = 'Andy'
咱们经过单引号引用一串字符串的时候这个字符串只是单纯的字符串,可是若是使用双引号引用,在字符串里面还支持插值操做,
def name = 'Andy' def greeting = "Hello, $name!"
相似 python 同样,经过def
关键字定义一个方法。方法若是不指定返回值,默认返回最后一行代码的值。
def square(def num) { num * num } square 4
Groovy 也是经过Groovy 定义一个类:
class MyGroovyClass { String greeting String getGreeting() { return 'Hello!' } }
pulic
的,全部类的字段都是private
的;new
关键字获得类的实例,使用def
接受对象的引用:def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
,注意,groovy 的方法调用是能够没有括号的,并且也不须要分号结尾。除此以外,咱们甚至也能够直接调用;instance.greeting
这样的方式拿到字段值,但其实这也会经过其get方法,并且不是直接拿到这个值。在 Groovy 中,定义一个列表是这样的:
List list = [1, 2, 3, 4, 5]
遍历一个列表是这样的:
list.each() { element -> println element }
定义一个 map 是这样的:
Map pizzaPrices = [margherita:10, pepperoni:12]
获取一个map 值是这样的:
pizzaPrices.get('pepperoni') pizzaPrices['pepperoni']
在Groovy 中有一个闭包的概念。闭包能够理解为就是 Java 中的匿名内部类。闭包支持相似lamda
形式的语法调用。以下:
def square = { num -> num * num } square 8
若是只有一个参数,咱们甚至能够省略这个参数,默认使用it
做为参数,最后代码是这样的:
Closure square = { it * it } square 16
理解闭包的语法后,咱们会发现,其实在咱们以前的配置文件里,android
,dependencies
这些后面紧跟的代码块,都是一个闭包而已。
了解完 groovy 的基本语法后,咱们来看看 gradle 里面的代码就好理解多了。
apply plugin: 'com.android.application'
这段代码其实就是调用了project
对象的apply
方法,传入了一个以plugin
为key的map。完整写出来就是这样的:project.apply([plugin: 'com.android.application'])
实际调用的时候会传入一个DependencyHandler
的闭包,代码以下:
运行该 task
./gradlew hello
注意:咱们前面说过,gradle的生命周期分三步,初始化,配置和执行。上面的代码在配置过程就已经执行了,因此,打印出的字符串发生在该任务执行以前,若是要在执行阶段才执行任务中的代码应该以下设置:
doFirst()
和doLast()
方法。打印出来是这样的:
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
便可同时执行两个任务。
咱们能够经过两个例子来实践task。
这里直接将 store 的密码明文写在这里对于产品的安全性来讲不太好,特别是若是该源码开源,别人就能够用你的 id 去发布app。对于这种状况,咱们须要构建一个动态加载任务,在编译release 源码的时候从本地文件(未加入git)获取keystore 信息,以下:
你还能够设置一个保险措施,万一咱们的没有找到对应的文件须要用户从控制台输入密码
最后设置最终值
而后设置release 任务依赖于咱们刚刚设置的任务
最后编译出来的apk 名字相似 app-debug-1.0.apk
。
来自:http://www.jianshu.com/p/9df3c3b6067a