Gradle高阶-Project详解2(属性相关)

2,133 阅读8分钟

前情回顾

上一节我们探讨了project中project类中的几个api,通过这几个api我们可以看到,我们一个project中所有的project并不是独立存在的而是相互关联的,我们可以对对工程进行遍历得到每个project,或者是project的路径得到对应的project。

新的篇章

首先先问几个问题?
①为什么每个module下都有一个build.gradle文件?
②为什么默认模式下打包生成的文件在一个叫build的文件夹下?
③为什么会存在gradle.properties文件?

看完本文后,你就能解答出上面的几个问题了
不要打我,先上一段代码

@HasInternalProtocol
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
    /**
     * The default project build file name.
     */
    String DEFAULT_BUILD_FILE = "build.gradle";

    /**
     * The hierarchy separator for project and task path names.
     */
    String PATH_SEPARATOR = ":";

    /**
     * The default build directory name.
     */
    String DEFAULT_BUILD_DIR_NAME = "build";

    String GRADLE_PROPERTIES = "gradle.properties";

    String SYSTEM_PROP_PREFIX = "systemProp";

    String DEFAULT_VERSION = "unspecified";

    String DEFAULT_STATUS = "release";
        …………
}

既然我们要学习Project的相关知识,那么我们先要了解下Project这个类里面到底有什么东西,上面的代码我列举了一部分,但是这一部分的内容已经可以解答上面的问题了。

DEFAULT_BUILD_FILE:我们的project默认就是从这个文件中读取相关配置,然后对它自己进行初始化配置            
PATH_SEPARATOR:文件分隔符,在Mac系统中,文件分隔符是反斜杠"\",而在gradle文件中,分隔符都是用冒号(不区分系统)      
DEFAULT_BUILD_DIR_NAME:默认生成的build文件 
GRADLE_PROPERTIES:姑且称为自定义属性文件   

简单对Project文件中的属性了解后,开始我们今天的新内容,Project在为我们提供了便利的属的同时也支持开发者对其进行扩展的操作,这样就能满足我们各种各样的构建需求了。现在开始进行我们的扩展之路。
在开始扩展之前我们先来看一个代码片段。通过该片段开始我们的扩展之旅。

apply plugin: 'com.android.application'
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.lcf.demo"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 32
    }

}

dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

以前我总是把build.gradle文件看成是一个配置文件,就相当于一个Maven或者Ant一样,在这个配置文件中直接写数值或者字符串是没有问题的,但是在学习了gradle,了解它是一个变成框架以后再后将build.gradle文件当成是一个类再来看的时候,这样写代码的方式真的有点low。 这种写法也被称之为魔术数(魔术数字是程式设计中所谓的直接写在程式码里的具体数值,如“10”“123”等以数字直接写出的值),那么这样看的话我们这个类中有大量的无用的字符串或者是数值,那么如何解决这个问题呢?
方法一:定义变量

apply plugin: 'com.android.application'
def mCompileSdkVersion=28
def libCompat='com.android.support:appcompat-v7:28.0.0'
android {
    compileSdkVersion mCompileSdkVersion
    defaultConfig {
        applicationId "com.lcf.demo"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 32
    }

}

dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    implementation libCompat
     implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

通过这种方式我们可以将所有的常量转变为有意义的变量,当然gradle是支持扩展操作的,那么我们可以将代码改成如下形式:

apply plugin: 'com.android.application'

ext{
     mCompileSdkVersion=28
     libCompat='com.android.support:appcompat-v7:28.0.0'
}
android {
    compileSdkVersion this.mCompileSdkVersion
    defaultConfig {
        applicationId "com.lcf.demo"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 32
    }

}

dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    implementation  this.libCompat
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

就这样我们通过ext这个关键字实现了扩展属性的操作,比较上面的代码,可能你会说,这也没有什么优势,就是代码改动了下位置,还多写了一部分呢?

且先莫急,的确,如果放在一个project下面,这种扩展的写法的确没有什么优势,但是大家想想,一个正常的项目是不可能只有一个project的,举个栗子,如果你的项目有5个project,而你采用的就是常量的写法,这个时候需要你修改的话,你就需要这5个类的每一处都需要改动,而且一个不小心的话,都有可能改错,或者加载lib失败。

这个时候你或许会说,就算是这样,那我不还是要改5个project中的ext闭包中的属性吗?
莫慌莫慌~,此时你还记得我们上一节中的getAllProjects和getSubprojects属性吗?

我们可以按照上节的内容来改造下,来来来,让我们荡起双桨,呸,让我们将代码撸起来,首先找到项目根工程的project文件

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
        google()
    

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
subprojects{
    ext{
        mCompileSdkVersion=28
        libCompat='com.android.support:appcompat-v7:28.0.0'
    }
}

此时我们将之前在project中写好的ext扩展删除

apply plugin: 'com.android.application'
android {
    compileSdkVersion this.mCompileSdkVersion
    defaultConfig {
        applicationId "com.lcf.demo"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 32
    }

}

dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    implementation  this.libCompat
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

然后我们编译下项目,验证下这种写法是否有效,经过编译后发现,这样的写法是对的,我们能成功使用在根工程中project定义subprojects中定义的ext为所有的子project添加配置属性,简直堪称完美。
本着对代码要求完美的心,说白了就是不作就不会死的心,还能不能把代码写的更好一点呢?上面的代码看似很完美,但是其实是不完美的。
虽然我们上面的代码只写了一次,只定义了一次,但是我们的gradle会给每一个子project都定义一次ext这个扩展属性,所以从本质上来说,我们的每一个project还是定义了一个ext的扩展属性,只不过现在的过程是由gradle帮我们进行了操作。
上节课我们说到getAllProjects,也就是在根工程中进行操作,子Project中所有属性都会继承父Project中的属性,那么我们的代码又可以改成下面的形式:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
        google()
    

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

ext{
        mCompileSdkVersion=28
        libCompat='com.android.support:appcompat-v7:28.0.0'
}
apply plugin: 'com.android.application'
android {
    compileSdkVersion this.rootProject.mCompileSdkVersion
    defaultConfig {
        applicationId "com.lcf.demo"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 32
    }

}

dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    implementation  this.rootProject.libCompat
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

到此时,我们的代码更改的更优了,但是还不是最优的,(OS:真™墨迹啊,就不能直接讲讲最优的嘛),其实讲了这么多,基本上都是大家常见的写法,说白了就是记录一个踩坑的过程。
重头戏来了,最优解决方案 不知道大家是否还记得上一节中有稍微提到的maven配置加载的内容

apply from:'../publishToMaven.gradle'

publishToMaven.gradle这个文件上节我们就说了,存放的是maven配置相关的内容,其实我们的最优方案就是和这个一样,自定义一个configs.gradle的文件,将所有的变量放到这个里面。

ext{
    android=[
        applicationId :"com.lcf.demo"
        minSdkVersion :17
        targetSdkVersion :28
        versionCode :32
    ]
    signConfigs=[
    ]
    java=[
    ]
    dependence=[
    
        'design': 'com.android.support:design:28.0.0'
        'support-v4': 'com.android.support:support-v4:28.0.0'
        'recyclerview': 'com.android.support:recyclerview-v7:28.0.0'
    ]
}

上面的代码按照了正真的build文件中使用的分组格式进行分组,这样定义让我们的分类更合理,让我们的在每个分组中都是用Map的key、value的形式定义的,在定义好这个文件后,我们只需要在根工程中引用即可。

apply from: this.file('configs.gradle')
buildscript {
    repositories {
        jcenter()
        google()

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

在根工程这引用后,我们就可以在子工程中使用了,且看如下代码:

apply plugin: 'com.android.application'
android {
    compileSdkVersion this.mCompileSdkVersion
    defaultConfig {
        applicationId rootProjet.ext.android.applicationId
        minSdkVersion rootProjet.ext.android.minSdkVersion
        targetSdkVersion rootProjet.ext.android.targetSdkVersion
        versionCode rootProjet.ext.android.versionCode
    }

}

dependencies {
    compile rootProjet.ext.dependence.design
    compile rootProjet.ext.dependence.support-v4
    compile rootProjet.ext.dependence.recyclerview
}

经过上面的一番操作,现在我们的gradle文件中几乎已经看不到字符串常量和int类型的常量了,而且你不觉得代码更加整洁了吗?并且修改起来,也只需要维护configs文件就好了。 到这里,第一种扩展属性的方式就介绍完了,下面开始我们的第二种方式。 还记得前面说的gradle.properties文件吗?对,这种方式就是用到他自身的这个文件,但是要记住,这个文件中的属性只能用key:value的形式,切忌用Map操作,比如我们要通过属性加载模块

isDebug=false
mCompileSdkVersion=25

在setting文件中操作如下:

//include ':Test'
//现在通过isDebug属性来确定是否加载该模块
if(hasProperty('isDebug')?isDebug.toBoolean():false){
    include ':Test'
}

此时,我们编译下项目,可以在看到Test项目已经不再是lib工程了,没有lib标识的小图标了,isDebug=true的时候就是lib了,注意,在这个文件中自定义中属性不能和已有的gradle的属性的名称一致,并且在使用的时候需要转换成对应的类型,否则会无法使用,比如要使用mCompileSdkVersion这个属性,我们就要写成这样

compileSdkVersion  mCompileSdkVersion.toInteger()

啰里啰嗦这么多,总算是将这两种加载扩展方式的方法说完了,下面小结一下。在小结之前,我们还是解答下开篇的问题,其实答案很简单,就是因为Project自身属性的内容决定如此(我认为是这样的,如果你有更好的回答欢迎留言告诉我)。

总结

  1. 回顾上节内容
  2. 学习project的扩展属性操作的两种方式,方法一:通过ext扩展,方法二:通过自身属性gradle.properties文件进行扩展操作

最后想说一句,gradle是真心的强大,你们觉得呢?