Gradle 完整指南(Android)

本文转载自 https://blog.csdn.net/qq_29825237/article/details/54017131html

前言

为何须要学Gradle?

Gradle 是 Android 如今主流的编译工具,虽然在Gradle 出现以前和以后都有对应更快的编译工具出现,可是 Gradle 的优点就在于它是亲儿子,Gradle 确实比较慢,这和它的编译过程有关,可是如今的Gradle 编译速度已经有了成倍提升。除此以外,相对其余编译工具,最重要的,他和 Android Studio 的关系很是紧密,能够说对于一些简单的程序咱们几乎不须要任何代码上的配置只使用 Android Studio 就能够完成编译和运行。java

可是对于一些比较复杂的,特别是多人团队合做的项目咱们会须要一些个性化的配置来提升咱们的开发效率。好比咱们要自定义编译出的apk包的名字、对于一些特殊产品咱们可能会要用同一个项目编译出免费版付费版的apk。这些高级的功能都须要咱们对配置代码进行自定义地修改。python

最近伴随着 Android Studio2.0的发布, Gradle 也进行了一次很是大的升级,叫Instant Run.它的编译速度网上有人用逆天两个字来形容。当咱们第一次点击run、debug按钮的时候,它运行时间和咱们往常同样。可是接下去的时间里,你每次修改代码后点击run、debug按钮,对应的改变将迅速的部署到你正在运行的程序上,传说速度快到你都来不及把注意力集中到手机屏幕上,它就已经作好相应的更改。可是刚出来的彷佛对一些项目的兼容性不太好,如今升级后不知道怎么样。android

为何要了解命令行编译?

在不少状况下咱们都是使用的 Android Studio 来build、debug项目。Android Studio 能知足咱们开发的大多数需求,可是某些状况下命令行可以让咱们编译的效率更高,过程更明朗,一些高级的配置也须要熟悉命令行才可以使用,好比在服务器编译,某些项目初始化的时候若是直接交给Android Studio ,它会一直Loading,你都不知道它在干吗,可是用命令行你就知道它卡在了哪一个环节,你只须要修改某些代码,立刻就可以编译过了。git

了解 Gradle 以后咱们能够作什么?

we can do everything what we want.编程

  • 自定义编译输出文件格式。
  • hook Android 编译过程。
  • 配置和改善 Gradle 编译速度

Gralde Overview

History

咱们知道,Android 的编译过程很是复杂:windows

咱们须要一种工具帮咱们更快更方便更简洁地完成 Android 程序的编译。如今结合Android Studio 咱们通常使用的工具都是Gradle, 在 Gradle 出现之前Android 也有对应的编译工具叫Ant,在Gradle 出现以后,也有新的编译工具出现,就是FaceBook 的Buck工具。这些编译工具在出现的时候几乎都比 Gradle 要快,Gradle 之因此慢是跟它的编译周期有很大关系。缓存

Gradle 的编译周期

在解析 Gradle 的编译过程以前咱们须要理解在 Gradle 中很是重要的两个对象。ProjectTask安全

每一个项目的编译至少有一个 Project,一个 build.gradle就表明一个project,每一个project里面包含了多个task,task 里面又包含不少actionaction是一个代码块,里面包含了须要被执行的代码。服务器

在编译过程当中, Gradle 会根据 build 相关文件,聚合全部的projecttask,执行task 中的 action。由于build.gradle文件中的task很是多,先执行哪一个后执行那个须要一种逻辑来保证。这种逻辑就是依赖逻辑,几乎全部的Task 都须要依赖其余 task 来执行,没有被依赖的task 会首先被执行。因此到最后全部的 Task 会构成一个有向无环图(DAG Directed Acyclic Graph)数据结构

编译过程分为三个阶段:

  • 初始化阶段:建立 Project 对象,若是有多个build.gradle,也会建立多个project.
  • 配置阶段:在这个阶段,会执行全部的编译脚本,同时还会建立project的全部的task,为后一个阶段作准备。
  • 执行阶段:在这个阶段,gradle 会根据传入的参数决定如何执行这些task,真正action的执行代码就在这里.

谢绝转载,非要转载,请注明出处http://www.jianshu.com/p/9df3c3b6067a

刚刚咱们提到Gradle 编译的时候的一些相关文件,下面咱们挨个解析一下这些文件。

Gradle Files

对于一个gradle 项目,最基础的文件配置以下:


一个项目有一个setting.gradle、包括一个顶层的 build.gradle文件、每一个Module 都有本身的一个build.gradle文件。

  • setting.gradle:这个 setting 文件定义了哪些module 应该被加入到编译过程,对于单个module 的项目能够不用须要这个文件,可是对于 multimodule 的项目咱们就须要这个文件,不然gradle 不知道要加载哪些项目。这个文件的代码在初始化阶段就会被执行。
  • 顶层的build.gradle:顶层的build.gradle文件的配置最终会被应用到全部项目中。它典型的配置以下:
buildscript {
    ext.kotlin_version = '1.2.20'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

 

  • buildscript:定义了 Android 编译工具的类路径。repositories中,jCenter是一个著名的 Maven 仓库。
  • allprojects:中定义的属性会被应用到全部 moudle 中,可是为了保证每一个项目的独立性,咱们通常不会在这里面操做太多共有的东西。

  • 每一个项目单独的 build.gradle:针对每一个moudle 的配置,若是这里的定义的选项和顶层build.gradle定义的相同,后者会被覆盖。典型的 配置内容以下:

  • apply plugin:第一行代码应用了Android 程序的gradle插件,做为Android 的应用程序,这一步是必须的,由于plugin中提供了Android 编译、测试、打包等等的全部task。
  • android:这是编译文件中最大的代码块,关于android 的全部特殊配置都在这里,这就是又咱们前面的声明的 plugin 提供的。
    • defaultConfig就是程序的默认配置,注意,若是在AndroidMainfest.xml里面定义了与这里相同的属性,会以这里的为主。
    • 这里最有必要要说明的是applicationId的选项:在咱们曾经定义的AndroidManifest.xml中,那里定义的包名有两个用途:一个是做为程序的惟一识别ID,防止在同一手机装两个同样的程序;另外一个就是做为咱们R资源类的包名。在之前咱们修改这个ID会致使全部用引用R资源类的地方都要修改。可是如今咱们若是修改applicationId只会修改当前程序的ID,而不会去修改源码中资源文件的引用。
  • buildTypes:定义了编译类型,针对每一个类型咱们能够有不一样的编译配置,不一样的编译配置对应的有不一样的编译命令。默认的有debug、release 的类型。
  • dependencies:是属于gradle 的依赖配置。它定义了当前项目须要依赖的其余库。

Gradle Wrapper

Gradle 不断的在发展,新的版本不免会对以往的项目有一些向后兼容性的问题,这个时候,gradle wrapper就应运而生了。

gradlw wrapper 包含一些脚本文件和针对不一样系统下面的运行文件。wrapper 有版本区分,可是并不须要你手动去下载,当你运行脚本的时候,若是本地没有会自动下载对应版本文件。

在不一样操做系统下面执行的脚本不一样,在 Mac 系统下执行./gradlew ...,在windows 下执行gradle.bat进行编译。

若是你是直接从eclipse 中的项目转换过来的,程序并不会自动建立wrapper脚本,咱们须要手动建立。在命令行输入如下命令便可

gradle wrapper --gradle-version 2.4gradle wrapper --gradle-version 2.4

它会建立以下目录结构:

wrapper 就是咱们使用命令行编译的开始。下面咱们看看 wrapper 有什么样的做用。

Gradle basics

Gradle 会根据build 文件的配置生成不一样的task,咱们能够直接单独执行每个task。经过./gradlew tasks列出全部task。若是经过同时还想列出每一个task 对应依赖的其余task,可使用./gradlew tasks -all

其实每当咱们在Android Studio点击 build,rebuild,clean菜单的时候,执行的就是一些gradle task.

Android tasks

有四个基本的 task, Android 继承他们分别进行了本身的实现:

  • assemble:对全部的 buildType 生成 apk 包。
  • clean:移除全部的编译输出文件,好比apk
  • check:执行lint检测编译。
  • build:同时执行assemblecheck命令

这些都是基本的命令,在实际项目中会根据不一样的配置,会对这些task 设置不一样的依赖。好比 默认的 assmeble 会依赖 assembleDebug 和assembleRelease,若是直接执行assmeble,最后会编译debug,和release 的全部版本出来。若是咱们只须要编译debug 版本,咱们能够运行assembleDebug

除此以外还有一些经常使用的新增的其余命令,好比 install命令,会将编译后的apk 安装到链接的设备。

咱们运行的许多命令除了会输出到命令行,还会在build文件夹下生产一份运行报告。好比check命令会生成lint-results.html.build/outputs中。

Configuration

BuildConfig

这个类相信你们都不会陌生,咱们最经常使用的用法就是经过BuildConfig.DEBUG来判断当前的版本是不是debug版本,若是是就会输出一些只有在 debug 环境下才会执行的操做。 这个类就是由gradle 根据 配置文件生成的。为何gradle 能够直接生成一个Java 字节码类,这就得益于咱们的 gradle 的编写语言是Groovy, Groovy 是一种 JVM 语言,JVM 语言的特征就是,虽然编写的语法不同,可是他们最终都会编程 JVM 字节码文件。同是JVM 语言的还有 Scala,Kotlin 等等。

这个功能很是强大,咱们能够经过在这里设置一些key-value对,这些key-value 对在不一样编译类型的 apk 下的值不一样,好比咱们能够为debug 和release 两种环境定义不一样的服务器。好比:

除此以外,咱们还能够为不一样的编译类型的设置不一样的资源文件,好比:

Repositories

Repositories 就是代码仓库,这个相信你们都知道,咱们平时的添加的一些 dependency 就是从这里下载的,Gradle 支持三种类型的仓库:Maven,Ivy和一些静态文件或者文件夹。在编译的执行阶段,gradle 将会从仓库中取出对应须要的依赖文件,固然,gradle 本地也会有本身的缓存,不会每次都去取这些依赖。

gradle 支持多种 Maven 仓库,通常咱们就是用共有的jCenter就能够了。
有一些项目,多是一些公司私有的仓库中的,这时候咱们须要手动加入仓库链接:

若是仓库有密码,也能够同时传入用户名和密码

咱们也可使用相对路径配置本地仓库,咱们能够经过配置项目中存在的静态文件夹做为本地仓库:

Dependencies

咱们在引用库的时候,每一个库名称包含三个元素:组名:库名称:版本号,以下:

若是咱们要保证咱们依赖的库始终处于最新状态,咱们能够经过添加通配符的方式,好比:

可是咱们通常不要这么作,这样作除了每次编译都要去作网络请求查看是否有新版本致使编译过慢外,最大的弊病在于咱们使用过的版本很很困难是测试版,性能得不到保证,因此,在咱们引用库的时候必定要指名依赖版本。

Local dependencies

File dependencies

经过files()方法能够添加文件依赖,若是有不少jar文件,咱们也能够经过fileTree()方法添加一个文件夹,除此以外,咱们还能够经过通配符的方式添加,以下:

Native libraries

配置本地 .so库。在配置文件中作以下配置,而后在对应位置创建文件夹,加入对应平台的.so文件。

文件结构以下:

Library projects

若是咱们要写一个library项目让其余的项目引用,咱们的bubild.gradle的plugin 就不能是andrid plugin了,须要引用以下plugin

apply plugin: 'com.android.library'apply plugin: 'com.android.library'

引用的时候在setting文件中include便可。

若是咱们不方便直接引用项目,须要经过文件的形式引用,咱们也能够将项目打包成aar文件,注意,这种状况下,咱们在项目下面新建arrs文件夹,并在build.gradle 文件中配置 仓库:

当须要引用里面的某个项目时,经过以下方式引用:

Build Variants

在开发中咱们可能会有这样的需求:

  • 咱们须要在debug 和 release 两种状况下配置不一样的服务器地址;
  • 当打市场渠道包的时候,咱们可能须要打免费版、收费版,或者内部版、外部版的程序。
  • 渠道首发包一般须要要求在欢迎页添加渠道的logo。等等
  • 为了让市场版和debug版同时存在与一个手机,咱们须要编译的时候自动给debug版本不同的包名。

这些需求都须要在编译的时候动态根据当前的编译类型输出不一样样式的apk文件。这时候就是咱们的buildType大展身手的时候了。

Build Type

android 默认的带有Debug和Release两种编译类型。好比咱们如今有一个新的statging的编译类型

Source sets

每当建立一个新的build type 的时候,gradle 默认都会建立一个新的source set。咱们能够创建与main文件夹同级的文件夹,根据编译类型的不一样咱们能够选择对某些源码直接进行替换。

除了代码能够替换,咱们的资源文件也能够替换

除此以外,不一样编译类型的项目,咱们的依赖均可以不一样,好比,若是我须要在staging和debug两个版本中使用不一样的log框架,咱们这样配置:

Product flavors

前面咱们都是针对同一份源码编译同一个程序的不一样类型,若是咱们须要针对同一份源码编译不一样的程序(包名也不一样),好比 免费版和收费版。咱们就须要Product flavors

注意,Product flavorsBuild 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:

Resource merge priority

在Build Type中定义的资源优先级最大,在Library 中定义的资源优先级最低。

Signing configurations

若是咱们打包市场版的时候,咱们须要输入咱们的keystore数据。若是是debug 版本,系统默认会帮咱们配置这些信息。这些信息在gradle 中都配置在signingConfigs中。


配置以后咱们须要在build type中直接使用

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来优化咱们的资源文件,除去那些没必要要的资源。

若是咱们须要查看该命令帮咱们减小了多少无用的资源,咱们也能够经过运行shrinkReleaseResources命令来查看log.

某些状况下,一些资源是须要经过动态加载的方式载入的,这时候我也须要像 Progard 同样对咱们的资源进行keep操做。方法就是在res/raw/下创建一个keep.xml文件,经过以下方式 keep 资源:

Manual shrinking

对一些特殊的文件或者文件夹,好比 国际化的资源文件、屏幕适配资源,若是咱们已经肯定了某种型号,而不须要从新适配,咱们能够直接去掉不可能会被适配的资源。这在为厂商适配机型定制app的时候是很用的。作法以下:
好比咱们可能有很是多的国际化的资源,若是咱们应用场景只用到了English,Danish,Dutch的资源,咱们能够直接指定咱们的resConfig:

对于尺寸文件咱们也能够这样作

Profiling

当咱们执行全部task的时候咱们均可以经过添加--profile参数生成一份执行报告在reports/profile中。示例以下:

咱们能够经过这份报告看出哪一个项目耗费的时间最多,哪一个环节耗费的时间最多。

Practice

在开发的过程当中,咱们可能会遇到不少状况须要咱们可以本身定义task,在自定义task 以前,咱们先简单看看groovy 的语法。

Groovy

咱们前面看到的那些build.gradle 配置文件,和xml 等的配置文件不一样,这些文件能够说就是能够执行的代码,只是他们的结构看起来通俗易懂,和配置文件没什么两样,这也是Google 之因此选择Groovy 的缘由。除此以外,Groovy 是一门JVM 语言,也就是,Groovy 的代码最终也会被编译成JVM 字节码,交给虚拟机去执行,咱们也能够直接反编译这些字节码文件。

咱们这里简单地说一下 groovy 一些语法。

变量

在groovy 中,没有固定的类型,变量能够经过def关键字引用,好比:

def name = 'Andy'

咱们经过单引号引用一串字符串的时候这个字符串只是单纯的字符串,可是若是使用双引号引用,在字符串里面还支持插值操做,

 
  1. def name = 'Andy'

  2. def greeting = "Hello, $name!"

方法

相似 Python 同样,经过def关键字定义一个方法。方法若是不指定返回值,默认返回最后一行代码的值。

 
  1. def square(def num) {

  2. num * num

  3. }

  4. square 4

Groovy 也是经过Groovy 定义一个类:

 
  1. class MyGroovyClass {

  2. String greeting

  3. String getGreeting() {

  4. return 'Hello!'

  5. }

  6. }

  • 在Groovy 中,默认全部的类和方法都是pulic的,全部类的字段都是private的;
  • 和java同样,咱们经过new关键字获得类的实例,使用def接受对象的引用:def instance = new MyGroovyClass()
  • 并且在类中声明的字段都默认会生成对应的setter,getter方法。因此上面的代码咱们能够直接调用instance.setGreeting 'Hello, Groovy!'注意,groovy 的方法调用是能够没有括号的,并且也不须要分号结尾。除此以外,咱们甚至也能够直接调用;
  • 咱们能够直接经过instance.greeting这样的方式拿到字段值,但其实这也会经过其get方法,并且不是直接拿到这个值。

map、collections

在 Groovy 中,定义一个列表是这样的:

List list = [1, 2, 3, 4, 5]List list = [1, 2, 3, 4, 5]

遍历一个列表是这样的:

 
  1. list.each() { element ->

  2.  
  3. println element

  4.  
  5. }

定义一个 map 是这样的:

Map pizzaPrices = [margherita:10, pepperoni:12]Map pizzaPrices = [margherita:10, pepperoni:12]

获取一个map 值是这样的:

 
  1. pizzaPrices.get('pepperoni')

  2. pizzaPrices['pepperoni']

闭包

在Groovy 中有一个闭包的概念。闭包能够理解为就是 Java 中的匿名内部类。闭包支持相似lamda形式的语法调用。以下:

 
  1. def square = { num ->

  2. num * num

  3. }

  4. square 8

若是只有一个参数,咱们甚至能够省略这个参数,默认使用it做为参数,最后代码是这样的:

 
  1. Closure square = {

  2. it * it

  3. }

  4. square 16

理解闭包的语法后,咱们会发现,其实在咱们以前的配置文件里,android,dependencies这些后面紧跟的代码块,都是一个闭包而已。

Groovy in Gradle

了解完 groovy 的基本语法后,咱们来看看 gradle 里面的代码就好理解多了。

  • apply
    apply plugin: 'com.android.application'apply plugin: 'com.android.application'
    这段代码其实就是调用了project对象的apply方法,传入了一个以plugin为key的map。完整写出来就是这样的:
project.apply([plugin: 'com.android.application'])project.apply([plugin: 'com.android.application'])
  • dependencies
    咱们看到的是这样:

实际调用的时候会传入一个DependencyHandler的闭包,代码以下:

Task

  • 建立一个task

运行该 task

./gradlew hello

注意:咱们前面说过,gradle的生命周期分三步,初始化,配置和执行。上面的代码在配置过程就已经执行了,因此,打印出的字符串发生在该任务执行以前,若是要在执行阶段才执行任务中的代码应该以下设置:

  • 添加Action:前面咱们说过task 包含系列的action,当task 被执行的时候,全部的action 都会被依次执行。若是咱们要加入本身的action,咱们能够经过复写doFirst()doLast()方法。

打印出来是这样的:

  • Task 依赖:前面咱们也说过,task 之间的关系就是依赖关系,关于Task 的依赖有两种,must RunAfterdependsOn。好比:
 
  1. task task1 <<{

  2. printfln 'task1'

  3. }

  4.  
  5. task task2 <<{

  6. printfln 'task2'

  7. }

  8. task2.mustRunAfter task1

 
  1. task task1 <<{

  2. printfln 'task1'

  3. }

  4.  
  5. task task2 <<{

  6. printfln 'task2'

  7. }

  8. task2.dependsOn task1

他们的区别是,运行的的时候前者必需要都按顺序加入gradlew task2 task1执行才能够顺利执行,不然单独执行每一个任务,后者只须要执行gradlew task2便可同时执行两个任务。

Practice

咱们能够经过两个例子来实践task。

keystore 保护

这里直接将 store 的密码明文写在这里对于产品的安全性来讲不太好,特别是若是该源码开源,别人就能够用你的 id 去发布app。对于这种状况,咱们须要构建一个动态加载任务,在编译release 源码的时候从本地文件(未加入Git)获取keystore 信息,以下:

你还能够设置一个保险措施,万一咱们的没有找到对应的文件须要用户从控制台输入密码

最后设置最终值

而后设置release 任务依赖于咱们刚刚设置的任务

经过 hook Android 编译插件 重命名 apk

最后编译出来的apk 名字相似 app-debug-1.0.apk

参考文献

  • Gadle For Android
相关文章
相关标签/搜索