能有那么一两句,说到你心里,惟愿足以!
Gradle是一款非常优秀的构建系统工具,它的DSL基于Groovy实现,可以让你很方便的通过代码控制这些DSL来达到你构建的目的。Gradle构建的大部分功能都是通过插件的方式来实现,所以非常灵活方便,如果内置插件不能满足的要求你可以自定义自己的插件。
DSL(Domain Specific Language)领域特定语言,及时专门关注某一领域的语言,她在于专,而不是全,所以才叫领域特定的,而不是像Java这用通用全面的语言。
Gradle就是一门DSL,它是基于Groovy的,专门解决自动化构建的DSL。开发者主要按照Gradle DSL定义的语法,书写相应的Gradle脚本就可以达到自动化构建的目的。
所谓wapper其实就是对gradle的一层包装,便于在团队开发过程中统一gradle构建的版本,这样大家都已使用统一的gradle 版本进行构建,避免因为gradle版本不统一带来的不必要问题。
我们在项目开发中,用的都是Wrapper这种方式,而不是自己下载zip压缩包,配置gradle的环境的方式,wrapper在Windows下是一个批处理脚本,在Linux下是一个shell脚本,当你使用wrapper启动gradle的时候,wrapper会检查gradle有没有被下载关联,如果没有将会从配置的地址(一般是gradle官方库)进行下载并运行构建。这对开发人员是非常方便,因为不用去专门配置环境,只要执行wrapper命令,它会帮你搞定一切。这种方式也方便我们在服务器上做持续集成(CI),因为我们不用再服务器上配置Gradle环境。
–gradle-version 用于指定使用的gradle版本
gradle-distribution-url 用于指定下载Gradle发行版的URL地址
该配置文件是gradle wrapper的相关配置文件,是由Wrapper Task生成的额,我们上述的配置都会写入该文件,字段说明如下:
#Tue May 01 18:18:48 CST 2018
#下载gradle压缩包解压后储存的主目录
distributionBase=GRADLE_USER_HOME
##相对于distributionBase的解压后的Gradle压缩包的路径
distributionPath=wrapper/dists
#同distributionBase,只不过是存放zip压缩包的
zipStoreBase=GRADLE_USER_HOME
#同distributionPath,只不过是存放zip压缩包的
zipStorePath=wrapper/dists
#Gradle发行版压缩包的下载地址
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
我们关注的就是distributionUrl这个字段,这个决定你的gradle wrapper依赖哪个gradle版本,我们通常都会把bin改成all,这样开发过程中就可以gradle的源码了。
在build.gradle构建文件中录入以下脚本:其中task并不是一个关键字,其实他是Project对象的一个函数,
task wrapper(type: Wrapper){
gradleVersion = '4.4'
archiveBase ='GRADLE_USER_HOME'
archivePath ='wrapper/dists'
distributionBase='GRADLE_USER_HOME'
distributionPath='wrapper/dists'
distributionUrl='https\\://services.gradle.org/distributions/gradle-4.4-all.zip'
}
这样在执行gradle wrapper的时候,就是默认生成4.4版本的wrapper,而不用使用-gradle-version 4.4进行指定了。
在Gradle中定义了一个设置文件,用于初始化以及工程树的配置。设置文件的默认名字是setting.gradle,放在根工程目录下。
设置文件大多数的作用都是为了配置子工程,在Gradle中多工程是通过工程树表示的,就相当于AS看到的project和module的概念一样。
一个子工程只有在setting文件里配置了Gradle才会被识别,才会在构建的时候被包含进去:
每个Project都会有一个Build文件,该文件是Project构建的入口,可以在这里针对该Project进行配置,比如配置版本,需要哪些插件,依赖哪些库等。rootProject也不例外,他可以获取到所有的childProject,所以在Root Project的Build文件里我们可以对Child Project统一配置,比如应用的插件,依赖的Maven中心库等。比如配置所有Child Project的仓库为jcenter,这样我们依赖的的jar包就可以从jcenter中心库中下载了:
allprojects {
repositories {
google()
jcenter()
}
}
除了allprojects还有subprojects,他们是两个方法,接受一个闭包(一段代码片)作为参数,对工程进行遍历,遍历的过程中调用我们自定义的闭包,所以我们可以在闭包里配置,打印,输出或者修改Project的属性都可以。
这就是插件,我们只需要按照他约定的方式,使用它提供的任务,方法,扩展,就可以对我们项目进行构建。
二进制插件就是实现了org.gradle.api.Plugin接口的插件,它们可以有plugin id,例如:
apply plugin :'java'
上边的语句就是把Java插件应用到我们的项目中了,’java’就是Java插件的plugin id,他是唯一的。对于Gradle自带的核心插件都有一个容易记住的短名,成为plugin id,
二进制插件一般都是被打包在一个jar里独立发布的,比如我们自定义的插件,在发布的时候我们也可以为其指定plugin id,这个plugin id 最好是一个全限定名字,就像包名那样,避免重复
应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它使用的是from关键字,后边紧跟的是一个脚本文件,可以是本地的,也可以是网络的(要使用HTTP URL )
apply from:'version.gradle'
version.gradle
//用ext标记自定义版本号属性
ext {
versionName='1.0.0'
versionCode=1
}
第三方发布的作为jar的二进制插件,我们在应用的时候,必须要现在buildscript{}里配置其classpath才能使用,这个不像Gradle为我们提供的内置插件。比如我们的Android Gradle插件,就属于Android发布的第三方插件,如果要使用就要先配置;
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
buildscript{}块是一个在构建项目之前,为项目进行前期准备和初始化相关配置依赖的地方,配置好所需的依赖,就可以应用插件了:
apply plugin: 'com.android.application'
要想使用第三方依赖,你要告诉Gradle如何找到这些依赖,也就是依赖的配置,一般情况下都是从仓库中查找我们需要的jar包,首先要告诉gradle我们要使用什么类型的仓库,这些仓库的位置,这样就知道从哪里去下载jar包
repositories {
flatDir {
dirs 'libs'//自定义把jar包发布到本地目录
}
maven { url "https://jitpack.io" }
mavenCentral()
}
3.0以上的AS用implementation和testImplementation,以下的AS已经弃用了compile和testCompile,我们用最新的规定说明
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
这里解释一下依赖的区别
完全等同于compile指令,依赖可以传递,没区别,你将所有的compile改成api,完全没有错。
这个指令的特点就是,对于使用了该命令编译的依赖,对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序,也就是将该依赖隐藏在内部,而不对外部公开。
简单的说,就是使用implementation指令的依赖不会传递。
依赖首先应该设置为implementation的,如果没有错,那就用implementation,如果有错,那么使用api指令。使用implementation会使编译速度有所增快。使用implementation会使编译速度有所增快:比如我在一个library中使用implementation依赖了gson库,然后我的主项目依赖了library,那么,我的主项目就无法访问gson库中的方法。这样的好处是编译速度会加快,我换了一个版本的Gson库,但只要library的代码不改动,就不会重新编译主项目的代码。
等同于provided,只在编译时有效,不会参与打包,不会包含到apk文件中。可以用来解决重复导入库的冲突
annotationProcessor与compileOnly都是只编译并不打入apk中,他俩到底有什么区别呢?扮演的角色不一样,annotationProcessor作用是编译时生成代码,编译完真的就不需要了,compileOnly是有重复的库,为的是剃除只保留一个库,最终还是需要的。
SourceSet是Java插件用来描述和管理源代码及其资源的一个抽象概念,是一个Java源代码文件和资源文件的集合。通过源集我们可以很方便的访问源代码目录,有了源集,我们就能针对不同的业务和应用对我们源代码进行分组,比如用于主要业务产品的main以及用于单元测试的test,Java插件在Project下为我们提供一个sourceSet属性以及一个sourceSets{}闭包来访问和配置源集。sourceSet{}闭包配置的都是SourceSet对象,以下就是设置main这个源集的Java源文件和资源文件的存放目录
sourceSets{
main{
java {
java.srcDirs = ['src/main/java']
}
resources {
java.srcDirs = ['src/main/resources']
}
}
}
从gradle的角度看,Android其实就是Gradle的一个第三方插件,从Android的角度看,Android插件是基于Gradle构建的。
这部分可以写在根工程的build.gradle脚本文件中,这样所有的子工程就不用重复配置了。
apply from:'version.gradle'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
}
}
moudle下的build.gradle脚本中就可以应用AndroidGradle插件了
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.gradle.ytf.gradleapplication"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
buildTypes {
release {
minifyEnabled false
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
以上所有配置对用的都是ProductFlavor类里的方法或者属性。
- buildTypes 是一个域对象,NamedDomainObjectContainer类型,和SourceSet一样,里边有release,debug等,
- minifyEnabled 是否为该构建类型启用混淆
- resConfigs ‘zh’//只保留zh资源其他非zh都不会被打包到apk中,是资源限定符
- proguardFiles,当我们启用混淆时,所使用的proguard的配置文件,我们通过他配置如何进行混淆,比如混淆的级别,哪些类和方法不进行混淆等。对应BuildType里的proguardFiles方法,可以接受一个可变参数。所以我们可以配置多个配置文件,比如
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
getDefaultProguardFile是Android扩展的一个方法,它可以获取你的AndroidSDK目录下默认的proguard配置文件。在android-sdk/tools/proguard/目录下,文件名及时我们传入的参数名字proguard-android.txt。全限定写法:android.getDefaultProguardFile,只需要传递一个文件名给这个方法,就会返回tool/proguard目录下的该文件的绝对路径,方法原型为:
public File getDefaultProguardFile(String name) {
if (!ProguardFiles.KNOWN_FILE_NAMES.contains(name)) {
extraModelInfo
.getSyncIssueHandler()
.reportError(
EvalIssueReporter.Type.GENERIC, ProguardFiles.UNKNOWN_FILENAME_MESSAGE);
}
return ProguardFiles.getDefaultProguardFile(name, project);
}
Android插件是基于Java的插件,所以Android插件基本上包含了所有Java插件的功能,包括集成的任务,比如assemble ,check,build等,还添加了以下任务
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gradle.ytf.gradleapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<uses-library
android:name="com.google.android.maps"
android:required="true">uses-library>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
application>
manifest>
这样我们就声明了需要使用maps这个共享库,声明之后,在安装apk包的时候,系统会根据我们的定义,帮助检测手机系统是否有我们需要的共享库,因为设置了android:required=”true”,如果手机系统不满足,将不能安装该应用。另外还有两种库,一种是add-ons库,大部分是第三方厂商或者公司开发,让开发者使用,不暴露具体标准实现,还有一类是optional库,一般用来兼容旧版本API,比如:org.apache.http.legacy,这是一个HTTPClient的库,从API23开始,标准SDK中不再包含HTTPclient库,要想使用,就要使用org.apache.http.legacy这个可选库。
// applicationVariants.all { variant ->
// if (variant.buildType.name == "release") {
// variant.outputs.each { output ->
// def appName = 'hibook'
// def oldFile = output.outputFile
// def buildName
// def releaseApkName
//
// variant.productFlavors.each { product ->
// buildName = product.name
// }
//
// releaseApkName = appName + '-' + buildName + '-' + getDate() + '.apk'
// output.outputFile = new File(oldFile.parent, releaseApkName)
// }
// }
// }
//AS3.0以上版本
android.applicationVariants.all { variant ->
variant.outputs.all {
if (variant.name.endsWith("Debug")) {
//debug包
outputFileName = "$applicationId _v${defaultConfig.versionName}_${getDate()}_code${defaultConfig.versionCode}_debug.apk"
} else {
//release包
variant.outputs.each { output ->
def appName = 'hibook'
def oldFile = output.outputFile
def buildName
def releaseApkName
variant.productFlavors.each { product ->
buildName = product.name
}
releaseApkName = appName + '-' + buildName + '-' + getDate() + '_release.apk'
// output.outputFile = new File(oldFile.parent, releaseApkName)
outputFileName =releaseApkName
// outputFileName = "$applicationId _v${defaultConfig.versionName}_code${defaultConfig.versionCode}_${getDate()}_release.apk"
}
}
}
}
}
//获取时间戳
static def getDate() {
def date = new Date()
def formattedDate = date.format('yyyy-MM-dd-HH_mm')
return formattedDate
}
把签名文件和密钥信息放到服务器上,打正式包的时候去读取即可。
signingConfigs{
def appStoreFile=System.getenv("STORE_FILE")//获取名字为“STORE_FILE”的环境变量的值
def appStroePassword=System.getenv("STORE_PASSWORD")
def appKeyAlias=System.getenv("KEY_ALIAS")
def appKeyPassword=System.getenv("KEY_PASSWORD")
// 当不能从环境变量中获得签名信息的时候,就使用项目中带的dubug签名
if(!appStoreFile||!appStroePassword||!appKeyAlias||!appKeyPassword){
appStoreFile="debug.keystore"
appStroePassword="android"
appKeyAlias="androiddebugkey"
appKeyPassword="android"
}
release{
storeFile file(appStoreFile)
storePassword appStroePassword
keyAlias appKeyAlias
keyPassword appKeyPassword
}
}
<meta-data android:value="Channel ID" android:name="UMENG_CHANNEL">meta-data>
动态配置AndroidManifest文件就是可以在构建过程中动态修改manifest文件的一些内容,比如上述的友盟统计,就是要指定渠道名Channel ID,通过使用manifestPlaceholder,Manifest占位符,manifestPlaceholder是ProductFlavor的一个属性,是一个map类型,所以我们可以同时配置多个占位符
android{
productFlavors{
google{
manifestPlaceholders.put("UMENG_CHANNEL","google")
}
baidu{
manifestPlaceholders.put("UMENG_CHANNEL","baidu")
}
}
}
上述例子我们就定义了两个渠道,Google和baidu,并且配置了他们的manifestPlaceholders,他们的key都是一样,就是我们在manifest文件中的占位符变量,在构建的时候它会把manifest文件中所有占位符变量为UMENG_CHANNEL的内容替换为manifestPlaceholders中对用的value值
使用:
productFlavors {
Baidu {
}
Server {
}
Xiaomi {
}
Huawei {
}
Vivo {
}
Oppo {
}
Tencent {
}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: flavor.name]
}
或者:
data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_VALUE}"/>
productFlavors {
Baidu {
}
Server {
}
Xiaomi {
}
Huawei {
}
Vivo {
}
Oppo {
}
Tencent {
}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: flavor.name]
}
对于android5.0之前系统的虚拟机,它只认识一个DEX,名字还必须是class.dex.所以必须在程序入口application处进行控制,如果没有自定义application,直接使用MultiDexApplication即可,首先添加依赖:
implementation ‘com.android.support:multidex:1.0.1’
build.gradle
defaultConfig {
applicationId "com.gradle.ytf.gradleapplication"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
multiDexEnabled true//启用multidex
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
在manifest文件中配置:(5.0以后不需要配置)
android:name="android.support.multidex.MultiDexApplication"
如果有自定义的Application,并且直接继承Application,那么直接改继承为MultiDexApplication即可
如果自定义的application是继承其他第三方提供的Application,就不能改变继承,重写attachBaseContex()方法
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(base);
}
android{
dexOptions{
incremental true }
}
javaMaxHeapSize '4g'
jumboMode true
preDexLibraries false
android项目一般分为库项目,应用项目,测试项目,对应Android Gradle的3中插件,分别是com.android.library、com.android.application、com.android.test。
库项目一般和Java库相似,多一些Android特有的资源配置,一般具有公用特性的类,资源等都可以抽象成一个库工程,可以被其他项目引用,还有一种情况,就是工程复杂,可以根据业务把工程分成一个个库项目,然后通过一个应用项目引用他们,组合起来就是一个复杂的APP了。
应用项目,一般只有一个,可以打包成apk,如果要发布不同特色的APP,但是又是同类APP,就会有多个应用项目,比如QQ的标准版,轻聊版等
测试项目,是为了对APP进行测试而创建的,是Android基于Junit提供的一种测试Android项目的框架方法。
在setting.gradle里边配置
include ':app',':libraries libs1',':libraries libs2'
如果路径太多可以指定配置
include ':libs1'
project(':libs1').projectDir = new File(rootDir,'chapter09/libs1')
通过如上方法我们直接指定项目的目录即可。这样我们整个多项目配置的架子就搭好了,增减项目可以模拟这个框架。
Android库项目引用和Gradle是一样的,都是通过dependencies实现:
dependencies { compile project(':libraries libs1')}
这样就引用了这个lib库项目沿用了Gradle的依赖关系。
android为我们提供了针对代码,资源的优化工具Lint,可以帮助我们检查出哪些资源没有被使用,哪些使用了新的api,哪些资源没有国际化等,会生成一份报告,告诉我们哪些需要优化。
lintOptions{
abortOnError false //用于配置Lint发现错误时是否退出Gradle构建
// check 'NewApi'//检测issue id 为'NewApi'属性是一个Set集合,方法对应两个,参数不同
def checkSet =new HashSet()
checkSet.add("NewApi")
checkSet.add("InlinedApi")
check=checkSet
// 使用方法传参check
check 'NewApi','InlinedApi'
}
}
public void check(String id){
check.add(id)
}
public void check(String...ids){
for (String id:ids) {
check(id)
}
}
checkAllWarnings true 检测所有警告的issue,包括哪些默认被关闭的issue,false则不检查
checkReleaseBuilds true 在release构建构成中,Lint是否检查知名的错误问题,默认是ture,一旦发现有‘fatal’级别的问题,release构建终止