Android Gradle配置入门

246 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

Gradle

Gradle 是一个构建工具,是一个高级构建工具包。管理依赖并可以自定义构建流程

优势

  • 代码和资源易于重用
  • 不管是针对多个APK发行版还是多种不同风格和产品定位的应用程序,都可以很容易创建应用程序的多个不同版本
  • 易于配扩展和自定义构建过程
  • 良好的IDE集成

Android的Gradle目录层级和作用

  • 项目层级下的build.gradle :配置项目的整体属性、像代码仓库、依赖的gradle插件版本
  • 模块build.gradle 配置当前module的编译参数
  • gradle-wrapper.properties 配置Gradle wrapper
  • gradle.properties 配置Gradle编译参数
  • settings.gradle gradle的多项目管理
  • local.properties 该项目的私有属性配置、如SDK位置

gradle 方法

长得像配置的方法调用 以下的方式都可以

classpath ('com.android.tools.build:gradle:4.2.1')
classpath 'com.android.tools.build:gradle:4.2.1'

println("abc")
println "abc"

我们也可以见到以下这样的形式

dependencies ({
  classpath ('com.android.tools.build:gradle:4.2.1')
  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
})

dependencies {
  classpath ('com.android.tools.build:gradle:4.2.1')
  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
}

方法里面的{}这个是叫闭包(closure) Java没有这种东西 有一种类似的叫lambda

Java的方法只能调用,因为这又是稍后被调用的回调方法。现在调不行,只能传递方法本身,让你本身去调用。Java不允许传递方法,这里Java为了实现方法传递,包了一层,把方法包进了一个匿名类对象里面,用对象传递过去。

 int methodA(){
     return 1;
 }
 
 void methodB(){
     //preOperate
     //在Java中 我们可以去调用方法 如果这个时候想要这里使用多个其他类似的方法 我们可以进行以下操作
     methodA()
     //afterOperate
 }
 int methodA(){
     return 1;
 }
  int methodC(){
     return 1;
 }
 //4 如果方法不能一开始就调用呢 需要执行一部分程序再调用 这个时候就需要传入方法 
 void methodB(int num){
     //preOperate
     //1 如果这里需求变了 有很多类似的方法 对使用的方法不确定 
     num
     //afterOperate
 }
 methodB(methodA()) //2 我们可以调用不同方法进行传值使用
 methodB(methodC()) //3 MethodB 可以用到不同的方法 
 int methodA(){
     return 1;
 }
 
 void methodB(Method someMethod){
     preOperate()
     //1 如果这里需求又变了 有很多类似的方法 对使用的方法不确定 
     //我们想在方法内的任意处调用,而不是一开始将方法调用后再传过来
     someMethod()
     afterOperate()
 }
 //2  传递方法
 methodB(methodA)
 methodB(MethodC)
 
interface Foo {
    //this is the interface declaration
    fun print()
}

class Bar : Foo {
    //Bar is an implementing class
    override fun print() {
        println("Bar")
    }
}

fun test(foo: Foo) { //the method accepts an interface
    foo.print() //and invokes it, the implementing class' print() will be invoked.
}


fun main(args: Array<String>) {
    test(Bar()) //invoke test() with the class implementing the interface
}


fun main(args: Array<String>) {
    test(object : Foo {
        override fun print() {
            
        }
    }) //invoke test() with the class implementing the interface
}

闭包 closure

在 groovy中 可以传递方法 可以被传递的方法 传递的是方法本身 如果闭包是最后一个参数 可以不加括号。

{ [closureParameters ->] statements}

[closureParameters ->]  参数列表部分 
statements 语句部分 

methodMissing

有的时候、我们点开方法、会发现类中没有定义相关的方法 。但是这个时候程序依旧可以运行、是因为groovy中有这个methodMissing。类中没有定义的方法 如果符合其中要求、将进行调用

     public Object methodMissing(String name, Object args) {
        Configuration configuration = configurationContainer.findByName(name)
        if (configuration == null) {
            if (!getMetaClass().respondsTo(this, name, args.size())) {
                throw new MissingMethodException(name, this.getClass(), args);
            }
        }

        Object[] normalizedArgs = GUtil.collectionize(args)
        if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
            return doAdd(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
        } else if (normalizedArgs.length == 1) {
            return doAdd(configuration, normalizedArgs[0], (Closure) null)
        }
        normalizedArgs.each {notation ->
            doAdd(configuration, notation, null)
        }
        return null;
    }
def demo = new Demo()
//这里可以调用test方法 并输出 虽然没有定义方法、但是符合methodMissing中的要求
demo.test()

class Demo{
 void methodMissing(String name,Object... args){
   if (name == "test"){
     println("test")
   }
 }
}

buildTypes 构建类型

可以在构建类型中设置不同的构建类型。在src目录下创建相应的文件。如下图所示。gradle会将不同构建类型的目录下的文件和main目录中的文件进行整合打包。不同构建类型下的文件不要和main目录(主目录)下的文件冲突。

buildTypes {
    debug{
    
    }
    release{
    
    }
    internal{
        
    }
}

image.png

我们可以通过切换不同active build variant 去切换不同构建类型

image.png

如果工程还需要细化打包方向的话。还可以通过定义flavorDimensionsproductFlavors

  • productFlavors 产品定位 产品走向
  • flavorDimensions 定位维度 ( 如消费维度 国家维度(像出海应用和国内应用))

可以通过以下代码类比配置

flavorDimensions 'cost', 'nation', 'age'
productFlavors {
  free{
    dimension 'cost'
  }
  charge{
    dimension 'cost'
  }
  china{
    dimension 'nation'
  }
  global{
    dimension 'nation'
  }
  minor{
    dimension 'age'
  }
  adult{
    dimension 'age'
  }
}

这个时候我们可以更加细化打包内容 、将不同维度、不同环境的内容打到一个包中。

我们可以通过切换不同active build variant 去切换不同构建类型

image.png

compile implementation api

配置如下

//app module  0级
implementation project(":library1")


//library1 module 1级
implementation project(":library2")

library2 module 2级

app模块依赖library1、library1依赖library2。那么app依赖library2(传递依赖)。app模块可以用library2的代码 如果app 用 retrofit 。retrofit中依赖了okhttp。 如果没有用传递依赖、那么app还要单独依赖okhttp。 但是有的时候我们进行三方库开发。内部使用了某些其他的库进行一些和功能使用无关的封装操作、我们并不想将这些操作暴露给其他模块。这个时候这种传递依赖就不好了。会对其他开发者使用造成困扰。

传递依赖还会导致依赖的模块重新编译、如library2改动、app需要重新编译 (.java 变成 .class 不是打包)没有传递依赖的话打包总时长快一些。

没有传递依赖导致的不需要重新编译对本地项目收益较大(不是主module)

  • compile、api 会传递依赖,api是compile的替代品 效果一样
  • implementation 不会传递依赖

Gradle Wrapper

image.png

一个自动配置,如果本地没有gradle ,下载一个用。否则用本地的 可以通过CMD输入gradlew进行执行

有Gradle Wrapper 就不需要每一个工程放一个gradle进去,只需要gradle进行配置。指定需要的版本进行下载就好了。不用每个工程都将gradle放入。通过gradle-wrapper.properties配置

settings.gradle

项目里的结构 对项目中的module进行列举

task

任务task

task创建

//可以这样 
Task demo = task(demo){
  println("Configuration")
}
demo.doLast{
  println("demo ")
}

----------------------
//这样也行 不定义对象接收task
task clean(type: Delete) {
  println 2
  delete rootProject.buildDir
  println 3
}

task执行内容 我们可以看一下以下代码的运行流程对task运行流程进行了解

task clean(type: Delete) {
  println 2
  delete rootProject.buildDir
  println 3

  doLast {
    println 4
  }
  doFirst {
    println 1
  }
}

执行 gradlew 运行结果如下

image.png

欸嘿 我们可以看到执行gradlew 输出了一下信息(这个时候我们没有执行clean任务 为什么输出了3?),我们可以看到输出的内容上面有一个Configuration project。这里在对工程进行配置。而这里输出的是配置信息。

执行 gradlew clean 运行结果如下

image.png

输出了配置信息 然后输出了doLast 和 doFirst中的内容

  • doLast 往配置阶段的列表里面的最后面插入任务
  • doFirst 往配置阶段的列表里面的最前面插入任务

那么 这段代码的执行结果是?

task clean(type: Delete) {
  println 2
  delete rootProject.buildDir
  println 3

  doLast {
    println 4
  }
  doFirst {
    println 1
  }
  doLast {
    println 5
  }
  doFirst {
    println 6
  }
}

输入结果如下所示

image.png

  • 配置信息 输出 2 3
  • clean task 中
  • 往最后插入一个输出4的任务| //4
  • 往最前面插入一个输入1的任务| 1//4
  • 往最后面插入一个输出5的| 1//4 5
  • 往最前面插入一个输出6| 6 1//4 5

因此。我们配置gradle task 。不是在task clean(type: Delete) {//}进行编写。在这里不管调用与否,都会执行(在project 配置阶段 )。我们需要在task clean(type: Delete) { doFirst{ // }} task clean(type: Delete) { doLast{ // }}中对任务进行编写 。这里面编写的内容会在task执行阶段运行。

dependsOn

任务依赖会决定任务运行的先后顺序,被依赖的任务会在定义依赖的任务之前执行、创建任务间的依赖关系如下所示

task hello{
  doLast {
    println("hello")
  }
}
// hello 被 world 依赖
task world(dependsOn: hello){
  doLast {
    println("world")
  }
}

//gradlew world  输出信息如下  hello会先执行
> Task :hello
hello

> Task :world
world

gralde执行的生命周期

  1. 初始化阶段 执行settings.gradle 确定工程的主project 和 子project
  2. 定义阶段 执行每个project的build.gradle 确定所有的task所组成的有向无环图
  3. 执行阶段 根据上一阶段得到的有向无环图,依据有向无环图来执行指定的task

在生命周期的不同阶段插入代码

  • 初始化阶段与定义阶段之间 即第一个结束之后 第二个开始之前 在setting.gradle的最后编写代码
  • 在定义阶段和执行阶段之间 可以在以下代码块中插入相关代码 (和网络 文件相关的操作)
afterEvaluate {
  // 插入定义阶段和执行阶段之间的代码
}