Android Gradle 完整指南—一站式服务

转载这篇文章,本文来自http://www.jianshu.com/p/9df3c3b6067a

前言

为什么需要学Gradle?

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

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

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

为什么要了解命令行编译?

在很多情况下我们都是使用的Android Studio 来build、debug项目。Android Studio 能满足我们开发的大多数需求,但是某些情况下命令行能够让我们编译的效率更高,过程更明朗,一些高级的配置也需要熟悉命令行才能够使用,比如在服务器编译,某些项目初始化的时候如果直接交给Android Studio ,它会一直Loading,你都不知道它在干嘛,但是用命令行你就知道它卡在了哪个环节,你只需要修改某些代码,马上就能够编译过了。

了解Gradle 之后我们可以做什么?

we can do everything what we want.

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

Gralde Overview

History

我们知道,Android 的编译过程非常复杂:
在这里插入图片描述
我们需要一种工具帮我们更快更方便更简洁地完成Android程序的编译。现在结合Android Studio我们一般使用的工具都是Gradle,在Gradle出现以前Android也有对应的编译工具叫Ant ,在Gradle出现之后,也有新的编译工具出现,就是FaceBook的Buck工具。这些编译工具在出现的时候几乎都比Gradle要快,Gradle之所以慢是跟它的编译周期有很大关系。

Gradle 的编译周期

在解析Gradle的编译过程之前我们需要理解在Gradle中非常重要的两个对象。ProjectTask

每个项目的编译至少有一个Project,一个build.gradle就代表一个project,每个project里面包含了多个task,task里面又包含很多action,action是一个代码块,里面包含了需要被执行的代码。

在编译过程中, Gradle会根据build相关文件,聚合所有的project和task,执行task中的action。因为build.gradle文件中的task非常多,先执行哪个后执行那个需要一种逻辑来保证。这种逻辑就是依赖逻辑,几乎所有的Task都需要依赖其他task来执行,没有被依赖的task会首先被执行。所以到最后所有的Task会构成一个有向无环图(DAG Directed Acyclic Graph)的数据结构。

编译过程分为三个阶段:

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

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

Gradle Files

对于一个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.tools.build:gradle:1.2.3'
    }
}

allprojects{
    repositories{
        jcenter()
    }
}
  • buildscript:定义了Android编译工具的类路径。repositories中, jCenter是一个著名的Maven仓库。

  • allprojects:中定义的属性会被应用到所有module中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。

  • 每个项目单独的build.gradle:针对每个module的配置,如果这里的定义的选项和顶层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.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:同时执行assemble和check命令

这些都是基本的命令,在实际项目中会根据不同的配置,会对这些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'

引用的时候在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 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:
在这里插入图片描述

Resource merge priority

在这里插入图片描述

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来优化我们的资源文件,除去那些不必要的资源。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_37618354/article/details/84307664