Android开发之Gradle 进阶Tasks深入了解

目录
  • 前言
  • 定义Task
    • register与create的区别
  • 查找Task
  • 配置Task
  • 将参数传递给Task构造函数
  • Task添加依赖
  • Task排序
  • Task添加说明
  • 跳过Task
    • 使用onlyIf
    • 使用 StopExecutionException
    • 禁用与启用Task
    • Task超时
  • Task支持增量编译
    • Task的输入输出
    • 自定义task类型
    • 声明输入输出的好处
    • 推断task依赖关系
    • 输入和输出验证
    • 并行task
    • 增量编译原理解析
  • 一些高端操作
    • 将@OutputDirectory链接到@InputFiles
    • 禁用up-to-date检查
    • 提供自定义up-to-date检查
  • Finalizer Task
  • 总结

前言

Gradle自定义Task看起来非常简单,通过tasks.register等API就可以轻松实现。但实际上为了写出高效的,可缓存的,不拖慢编译速度的task,还需要了解更多知识。

本文主要包括以下内容:

  • 定义Task
  • 查找Task
  • 配置Task
  • 将参数传递给Task构造函数
  • Task添加依赖
  • Task排序
  • Task添加说明
  • 跳过Task
  • Task支持增量编译
  • Finalizer Task

定义Task

如上所说,自定义Task一般可以通过register API实现

tasks.register("hello") {
    doLast {
        println("hello")
    }
}
tasks.register<Copy>("copy") {
    from(file("srcDir"))
    into(buildDir)
}

如果是kotlin或者kts中,也可以通过代理来实现

val hello by tasks.registering {
    doLast {
        println("hello")
    }
}
val copy by tasks.registering(Copy::class) {
    from(file("srcDir"))
    into(buildDir)
}

register与create的区别

除了上面介绍的register,其实create也可以用于创建Task,那么它们有什么区别呢?

  • 通过register创建时,只有在这个task被需要时才会真正创建与配置该Task(被需要是指在本次构建中需要执行该Task)
  • 通过create创建时,则会立即创建与配置该Task

总得来说,通过register创建Task性能更好,更推荐使用

查找Task

我们有时需要查找Task,比如需要配置或者依赖某个Task,我们可以通过named方法来查找对应名字的task

tasks.register("hello")
tasks.register<Copy>("copy")
println(tasks.named("hello").get().name) // or just 'tasks.hello' if the task was added by a plugin
println(tasks.named<Copy>("copy").get().destinationDir)

也可以使用tasks.withType()方法来查找特定类型的Task

tasks.withType<Tar>().configureEach {
    enabled = false
}
tasks.register("test") {
    dependsOn(tasks.withType<Copy>())
}

除了上述方法,也可以通过tasks.getByPath()方法来查找task,不过这种方式破坏了configuration avoidance和project isolation,因此不被推荐使用

配置Task

在创建了Task之后,我们常常需要配置Task

我们可以在查找到Task之后进行配置

tasks.named<Copy>("myCopy") {
    from("resources")
    into("target")
    include("**/*.txt", "**/*.xml", "**/*.properties")
}

我们还可以将Task引用存储在变量中,并用于稍后在脚本中进一步配置任务。

val myCopy by tasks.existing(Copy::class) {
    from("resources")
    into("target")
}
myCopy {
    include("**/*.txt", "**/*.xml", "**/*.properties")
}

我们也可以在定义Task时进行配置,这也是最常用的一种

tasks.register<Copy>("copy") {
   from("resources")
   into("target")
   include("**/*.txt", "**/*.xml", "**/*.properties")
}

将参数传递给Task构造函数

除了在Task创建后配置参数,我们也可以将参数传递给Task的构建函数,为了实现这点,我们必须使用@Inject注解

abstract class CustomTask @Inject constructor(
    private val message: String,
    private val number: Int
) : DefaultTask()

然后,我们可以创建一个Task,在参数列表的末尾传递构造函数参数。

tasks.register<CustomTask>("myTask", "hello", 42)

需要注意的是,在任何情况下,作为构造函数参数传递的值都必须是非空的。如果您尝试传递一个null值,Gradle 将抛出一个NullPointerException指示哪个运行时值是null.

Task添加依赖

有几种方法可以定义Task的依赖关系,首先我们可以通过名称定义依赖项

project("project-a") {
    tasks.register("taskX") {
        dependsOn(":project-b:taskY")
        doLast {
            println("taskX")
        }
    }
}
project("project-b") {
    tasks.register("taskY") {
        doLast {
            println("taskY")
        }
    }
}

其次我们也可以通过Task对象定义依赖项

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
taskX {
    dependsOn(taskY)
}

还有一些更高端的用法,我们可以用provider懒加载块来定义依赖项,在evaluated阶段,provider被传递给正在计算依赖的task

provider块应返回单个对象Task或Task对象集合,然后将其视为任务的依赖项,如下所示:taskx添加了所有以lib开头的对象

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
// Using a Gradle Provider
taskX {
    dependsOn(provider {
        tasks.filter { task -> task.name.startsWith("lib") }
    })
}
tasks.register("lib1") {
    doLast {
        println("lib1")
    }
}tasks.register("lib2") {
    doLast {
        println("lib2")
    }
}
tasks.register("notALib") {
    doLast {
        println("notALib")
    }
}

Task排序

有时候,两个task之间没有依赖关系,但是对两个task的执行顺序却有所要求

任务排序和任务依赖之间的主要区别在于,排序规则不会影响将执行哪些任务,只会影响它们的执行顺序。

任务排序在许多场景中都很有用:

  • 强制执行任务的顺序:例如,build 永远不会在clean 之前运行。
  • 在构建的早期运行构建验证:例如,在开始发布构建工作之前验证我是否拥有正确的凭据。
  • 通过在长时间验证任务之前运行快速验证任务来更快地获得反馈:例如,单元测试应该在集成测试之前运行。
  • 聚合特定类型的所有任务的结果的任务:例如测试报告任务组合所有已执行测试任务的输出。

gradle提供了两个可用的排序规则:mustRunAfter 和 shouldRunAfter

当您使用mustRunAfter排序规则时,您指定taskB必须始终在taskA之后运行,这表示为taskB.mustRunAfter(taskA)

而shouldRunAfter规则理加弱化,因为在两种情况下这条规则会被忽略。一是使用这条规则会导致先后顺序成环的情况,二是当并行执行task,并且任务的所有依赖关系都已经满足时,那么无论它的shouldRunAfter依赖关系是否已经运行,这个任务都会运行。

因此您应该在排序有帮助但不是严格要求的情况下使用shouldRunAfter

示例如下:

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
// mustRunAfter
taskY {
    mustRunAfter(taskX)
}
// shouldRunAfter
taskY {
    shouldRunAfter(taskX)
}

需要注意的是,B.mustRunAfter(A)或B.shouldRunAfter(A)并不意味着任务之间存在任何执行依赖关系:

我们可以独立执行A或者任务B。排序规则仅在两个任务都计划执行时才有效。

Task添加说明

您可以为Task添加说明。执行时gradle tasks时会显示此说明。

tasks.register<Copy>("copy") {
   description = "Copies the resource directory to the target directory."
   from("resources")
   into("target")
   include("**/*.txt", "**/*.xml", "**/*.properties")
}

跳过Task

gradle提供了多种方式来跳过task的执行

使用onlyIf

你可以通过onlyIf为任务的执行添加条件,如果任务应该执行,则应该返回 true,如果应该跳过任务,则返回 false

val hello by tasks.registering {
    doLast {
        println("hello world")
    }
}
hello {
    onlyIf { !project.hasProperty("skipHello") }
}

Output of gradle hello -PskipHello
> gradle hello -PskipHello
> Task :hello SKIPPED

如上所示,hello任务被跳过了

使用 StopExecutionException

如果跳过任务逻辑不能使用onlyIf实现,您可以使用StopExecutionException。如果某个Action抛出此异常,则跳过该Action的进一步执行以及该任务的任何后续Action的执行。构建继续执行下一个任务。

val compile by tasks.registering {
    doLast {
        println("We are doing the compile.")
    }
}
compile {
    doFirst {
        // Here you would put arbitrary conditions in real life.
        if (true) {
            throw StopExecutionException()
        }
    }
}
tasks.register("myTask") {
    dependsOn(compile)
    doLast {
        println("I am not affected")
    }
}

禁用与启用Task

每个任务都有一个enabled的标志位,默认为true。将其设置为false可以阻止执行任何Task的执行。禁用的任务将被标记为 SKIPPED。

val disableMe by tasks.registering {
    doLast {
        println("This should not be printed if the task is disabled.")
    }
}
disableMe {
    enabled = false
}

Task超时

每个Task都有一个timeout属性,可用于限制其执行时间。当一个任务达到它的超时时间时,它的任务执行线程被中断。该任务将被标记为失败。但是Finalizer Task任务仍将运行。

如果构建时使用了--continue参数,其他任务可以在它之后继续运行。不响应中断的task不能超时。Gradle 的所有内置task都会及时响应超时

Task支持增量编译

任何构建工具的一个重要部分是避免重复工作。在编译过程中,就是在编译源文件后,除非发生了影响输出的更改(例如源文件的修改或输出文件的删除),无需重新编译它们。因为编译可能会花费大量时间,因此在不需要时跳过该步骤可以节省大量时间。

Gradle 支持增量构建,当您运行构建时,有些Task被标记为UP-TO-DATE,这就是增量编译生效了

那么Gradle增量编译如何工作?自定义Task如何支持增量编译?我们一起来看看

Task的输入输出

Task最基本的功能就是接受一些输入,进行一系列运算后生成输出。比如在编译过程中,Java源文件是输入,生成的classes文件是输出。其他输入可能包括诸如是否应包含调试信息之类的内容。

task输入的一个重要特征是它会影响一个或多个输出,从上图中可以看出。根据源文件的内容和target jdk版本,会生成不同的字节码。这使他们成为task输入。

但是编译期的一些其他属性,比如编译最大可用内存,由memoryMaximumSize属性决定,memoryMaximumSize对生成的字节码没有影响。因此,memoryMaximumSize不是task输入,它只是一个内部task属性。

作为增量构建的一部分,Gradle 会检查自上次构建以来是task的输入或输出有没有发生变化。如果没有,Gradle 可以认为task是最新的,因此跳过执行其action。需要注意的是,除非task至少有一个task输出,否则增量构建将不起作用

总得来说:

您需要告诉 Gradle 哪些task属性是输入,哪些是输出。

如果task属性影响输出,请务必将其注册为输入,否则该任务将被认为是最新的而不是最新的。

相反,如果属性不影响输出,则不要将其注册为输入,否则任务可能会在不需要时执行。

还要注意可能为完全相同的输入生成不同输出的非确定性task:不应将这些任务配置为增量构建,因为最新检查将不起作用。

接下来让我们看看如何将task属性注册为输入和输出。

自定义task类型

为了让自定义task支持增量编译,只需要以下两个步骤

  • 为每个task输入和输出创建类型化属性(通过 getter 方法)
  • 为每个属性添加适当的注解

Gradle 支持四种主要的输入和输出类型:

  • 简单值
    例如字符串和数字类型。更一般地说,任何一个实现了Serializable的类型。
  • 文件系统类型
    包括RegularFile,Directory和标准File类,也包括 Gradle 的FileCollection类型的派生类,以及任何可以被Project.file(java.lang.Object)和Project.files(java.lang.Object...)方法接收的参数
  • 依赖解析结果
    这包括包含Artifact元数据的ResolvedArtifactResult类型和包含依赖图的ResolvedComponentResult类型。请注意,它们仅支持包装在Provider中.
  • 包装类型
    不符合其他几个类型但具有自己的输入或输出属性的自定义类型。task的输入或输出包装在这些自定义类型中。

接下来我们看个例子

假设您有一个task处理不同类型的模板,例如 FreeMarker、Velocity、Moustache 等。它获取模板源文件并将它们与一些模型数据结合以生成不同结果。

此任务将具有三个输入和一个输出:

  • 模板源文件
  • 模型数据
  • 模板引擎
  • 输出文件的写入位置

在编写自定义task类时,我们很容易通过注解将属性注册为输入或输出

public abstract class ProcessTemplates extends DefaultTask {
    @Input
    public abstract Property<TemplateEngineType> getTemplateEngine();
    @InputFiles
    public abstract ConfigurableFileCollection getSourceFiles();
    @Nested
    public abstract TemplateData getTemplateData();
    @OutputDirectory
    public abstract DirectoryProperty getOutputDir();
    @TaskAction
    public void processTemplates() {
        // ...
    }
}
public abstract class TemplateData {
    @Input
    public abstract Property<String> getName();
    @Input
    public abstract MapProperty<String, String> getVariables();
}

可以看出,我们定义了3个输入,一个输出

  • templateEngine,表示使用什么模板引擎,我们传入一个枚举类型,枚举类型都实现了Serializable,因此可作为输入
  • sourceFiles,表示源文件,我们传入FileCollection作为输入
  • templateData,表示模型数据,自定义类型,在它的内部包装了真正的输入,通过@Nested注解表示
  • outputDir,表示输出目录,表示单个目录的属性需要@OutputDirectory注解

当我们重复运行以上task之后,就可以看到以下输出

> gradle processTemplates
> Task :processTemplates UP-TO-DATE
BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 up-to-date

如上所示,task在执行过程中会判断输入输出有没有发生变化,由于task的输入输出都没有发生变化,该task可以直接跳过,展示为up-to-date

除了上述几种注解,还有其他常用注解如@Internal,@Optional,@Classpath等,具体可查看文档:Incremental build property type annotations

声明输入输出的好处

一旦你声明了一个task的正式输入和输出,Gradle 就可以推断出关于这些属性的一些事情。例如,如果一个task的输入设置为另一个task的输出,这意味着第一个task依赖于第二个,gradle可以推断出这一点并添加隐式依赖

推断task依赖关系

想象一个归档task,会将processTemplates task的输出归档。可以看到归档task显然需要processTemplates首先运行,因此可能会添加显式的dependsOn. 但是,如果您像这样定义归档task:

tasks.register<Zip>("packageFiles") {
    from(processTemplates.map {it.outputs })
}

Gradle 会自动使packageFiles依赖processTemplates。它可以这样做是因为它知道 packageFiles 的输入之一需要 processTemplates 任务的输出。我们称之为推断的task依赖。

上面的例子也可以写成

tasks.register<Zip>("packageFiles2") {
    from(processTemplates)
}

这是因为from()方法可以接受task对象作为参数。然后在幕后,from()使用project.files()方法包装参数,进而将task的正式输出转化为文件集合

输入和输出验证

增量构建注解为 Gradle 提供了足够的信息来对带注解的属性执行一些基本验证。它会在task执行之前对每个属性执行以下操作:

  • @InputFile- 验证属性是否有值,并且路径是否对应于存在的文件(不是目录)。
  • @InputDirectory- 与@InputFile相同,但路径必须对应于目录。
  • @OutputDirectory- 验证路径是否是个目录,如果该目录尚不存在,则创建该目录。

如果一个task在某个位置产生输出,而另一个任务task将其作为输入使用,则 Gradle 会检查消费者任务是否依赖于生产者任务。当生产者和消费者任务同时执行时,构建就会失败。

此类验证提高了构建的稳健性,使您能够快速识别与输入和输出相关的问题。

您偶尔会想要禁用某些验证,特别是当输入文件可能实际上不存在时。这就是 Gradle 提供@Optional注释的原因:您使用它来告诉 Gradle 特定输入是可选的,因此如果相应的文件或目录不存在,则构建不应失败。

并行task

定义task输入和输出的另一个好处是:当使用--parallel选项时,Gradle 可以使用此信息来决定如何运行task。

例如,Gradle 将在选择下一个要运行的任务时检查task的输出,并避免并发执行写入同一输出目录的任务。

同样,当另一个task正在运行消耗或创建一些文件时,Gradle 将使用有关task销毁哪些文件的信息(例如,由Destroys注释)来避免运行删除这些文件的task,反之亦然。

它还可以确定创建一组文件的task已经运行,并且使用这些文件的task尚未运行,并且将避免在这中间运行删除这些文件的task。

总得来说,通过以这种方式提供task的输入和输出信息,Gradle 可以推断task之间的创建/消费/销毁关系,并可以确保task执行不会违反这些关系。

增量编译原理解析

上面我们介绍了如何自定义一个支持增量编译的task,那么它的原理是什么呢?

在第一次执行task之前,Gradle 会获取输入的指纹。该指纹包含输入文件的路径和每个文件内容的哈希值。Gradle 然后执行task。如果任务成功完成,Gradle 会获取输出的指纹。该指纹包含一组输出文件和每个文件内容的哈希值。Gradle 会在下次执行task时保留两个指纹。

之后每次执行task之前,Gradle 都会获取输入和输出的新指纹。如果新指纹与之前的指纹相同,Gradle 会假定输出是最新的并跳过该task。如果它们不相同,Gradle 将执行task。Gradle 会在下次执行task时保留两个指纹。

如果文件的统计信息(即lastModified和size)没有改变,Gradle 将重用上次运行的文件指纹。这意味着当文件的统计信息没有更改时,Gradle 不会检测到更改。

Gradle 还将task的代码视为task输入的一部分。当task、其操作或其依赖项在执行之间发生变化时,Gradle 会认为该task已过期。

Gradle 了解文件属性(例如,包含 Java classpath 的属性)是否是顺序敏感的。在比较此类属性的指纹时,即使文件顺序发生变化也会导致task过时。

请注意,如果task指定了输出目录,则自上次执行以来添加到该目录的任何文件都将被忽略,并且不会导致任务过期。这是因为不相关的任务可以共享一个输出目录而不会相互干扰。如果由于某种原因这不是您想要的行为,请考虑使用TaskOutputs.upToDateWhen(groovy.lang.Closure)

一些高端操作

上面介绍的内容涵盖了您将遇到的大多数用例,但有些场景需要特殊处理

将@OutputDirectory链接到@InputFiles

当您想将一个task的输出链接到另一个task的输入时,类型通常匹配,例如,File可以将输出属性分配给File输入。

不幸的是,当您希望将一个task的@OutputDirectory中的文件作为另一个task的@InputFiles属性(类型FileCollection)的源时,这种方法就会失效。

例如,假设您想使用 Java 编译task的输出(通过destinationDir属性)作为自定义task的输入,该task检测一组包含 Java 字节码的文件。这个自定义task,我们称之为Instrument,有一个使用@InputFiles注解的classFiles属性。您最初可能会尝试像这样配置task:

tasks.register<Instrument>("badInstrumentClasses") {
    classFiles.from(fileTree(tasks.compileJava.map { it.destinationDir }))
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

这段代码没有明显的问题,但是您如果实际运行的话可以看到compileJava并没有执行。在这种情况下,您需要通过dependsOn在instrumentClasses和compileJava之间添加显式依赖。因为使用fileTree()意味着 Gradle 无法推断task依赖本身。

一种解决方案是使用TaskOutputs.files属性,如以下示例所示:

tasks.register<Instrument>("instrumentClasses") {
    classFiles.from(tasks.compileJava.map { it.outputs.files })
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

或者,您可以使用project.files(),project.layout.files(),project.objects.fileCollection()来代替project.fileTree()

tasks.register<Instrument>("instrumentClasses2") {
    classFiles.from(layout.files(tasks.compileJava))
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

请记住files(),layout.files()和objects.fileCollection()可以将task作为参数,而fileTree()不能。

这种方法的缺点是源task的所有文件输出都成为目标task的输入文件。如果源task只有一个基于文件的输出,就像JavaCompile一样,那很好。但是如果你必须在多个输出属性中选择一个,那么你需要明确告诉 Gradle 哪个task使用以下builtBy方法生成输入文件:

tasks.register<Instrument>("instrumentClassesBuiltBy") {
    classFiles.from(fileTree(tasks.compileJava.map { it.destinationDir }) {
        builtBy(tasks.compileJava)
    })
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

当然你也可以通过dependsOn添加明确的task依赖,但是上面的方法提供了更多的语义,解释了为什么compileJava必须预先运行。

禁用up-to-date检查

Gradle 会自动处理对输出文件和目录的up-to-date检查,但如果task输出完全是另一回事呢?也许它是对 Web 服务或数据库表的更新。或者有时你有一个应该始终运行的task。

这就是doNotTrackState的作用,可以使用它来完全禁用task的up-to-date检查,如下所示:

tasks.register<Instrument>("alwaysInstrumentClasses") {
    classFiles.from(layout.files(tasks.compileJava))
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
    doNotTrackState("Instrumentation needs to re-run every time")
}

如果你的自定义task需要始终运行,那么您也可以在任务类上使用注解@UntrackedTaskTask

提供自定义up-to-date检查

Gradle 会自动处理对输出文件和目录的up-to-date检查,但如果task输出是对 Web 服务或数据库表的更新。在这种情况下,Gradle 无法知道如何检查task是否是up-to-date的。

这是就是TaskOutputs.upToDateWhen方法的作用,使用它我们就可以自定义up-to-date检查的逻辑。例如,您可以从数据库中读取数据库模式的版本号。或者,您可以检查数据库表中的特定记录是否存在或已更改。

请注意,up-to-date检查应该节省您的时间。不要添加比task的标准执行花费更多时间的检查。事实上,如果一个task经常需要运行,因为它很少是up-to-date的,那么它可能根本不值得进行up-to-date检查,如禁用up-to-date中所述。

一个常见的错误是使用upToDateWhen()而不是Task.onlyIf(). 如果您想根据与task输入和输出无关的某些条件跳过任务,那么您应该使用onlyIf(). 例如,如果您想在设置或未设置特定属性时跳过task

Finalizer Task

我们常常使用dependsOn来在一个task之前做一些工作,但如果我们想要在task执行之后做一些操作,该怎么实现呢?

这里我们可以用到finalizedBy方法

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
taskX { finalizedBy(taskY) }

如上所示,taskY将在taskX之后执行,需要注意的是finalizedBy并不是依赖关系,就算taskX执行失败,taskY也将正常执行

Finalizer task在构建创建的资源无论构建失败还是成功都必须清理的情况下很有用,一个示例是在集成测试任务中启动的 Web 容器,即使某些测试失败,也应该始终关闭它。这样看来finalizedBy类似java中的finally

要指定Finalizer task,请使用Task.finalizedBy(java.lang.Object...​)方法。此方法接受task实例、task名称或Task.dependsOn(java.lang.Object...​)接受的任何其他输入

总结

到这里这篇文章已经相当长了,gradle自定义task上手非常简单,但实际上有非常多的细节,尤其是要支持增量编译时。总得来说,为了写出高效的,可缓存的,不拖慢编译速度的task,还是有必要了解一下这些知识的

参考资料

docs.gradle.org/current/use…

以上就是Android开发之Gradle 进阶Tasks深入了解的详细内容,更多关于Android开发Gradle Tasks的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Studio 中Gradle配置sonarqube插件(推荐)

    目录 一,使用公共Maven仓库: 二,使用私有Maven仓库: Sonarqube作为一个很实用的静态代码分析工具,在很多项目中都使用.Android自然也不例外.这里就分享下使用Android Studio时如何在Gradle里配置Sonarqube. 以下分别就使用公共maven仓库和私有maven仓库两种情况来简单说明下: 一,使用公共Maven仓库: 这个比较简单. 打开gradle sonarqube插件官方网址:https://plugins.gradle.org/plugin/o

  • Androd Gradle模块依赖替换使用技巧

    目录 背景 解决 步骤1: 步骤2: 步骤3: 背景 我们在多模块项目开发过程中,会遇到这样的场景,工程里依赖了一个自己的或者其他同事的 aar 模块,有时候为了开发调试方便,经常会把 aar 改为本地源码依赖,开发完毕并提交的时候,会再修改回 aar 依赖,这样就会很不方便,开发流程图示如下: 解决 一开始我们通过在 app 的 build.gradle 里的 dependency 判断如果是需要本地依赖的 aar,就替换为 implementation project 依赖,伪代码如下: d

  • Android开发gradle拉取依赖的加速配置

    目录 前言 情况1 : 情况2: 前言 镜像配置都是常规操作,必要时也可以上代理. 自己搭的nexus本质也是一种镜像,可以代理maven中央仓库. 各个仓库的测速,可以使用这个脚本: 通过测速,调整仓库的顺序 apply from: 'https://raw.githubusercontent.com/hss01248/flipperUtil/master/deps/depsLastestChecker.gradle' 情况1 : 每次点击sync project with gradle fi

  • Android项目中gradle的执行流程

    目录 gradle文件执行流程 自定义gradle文件的导入方法 gradle中定义的变量如何被java代码使用 gradle文件执行流程 做过Android开发的同学都知道 ,Android项目中存在三个gradle文件,那你是否知道他们的执行流程呢?请看下面这张图: 为了验证结论 的正确性,我们采用输出字符串的验证方式: 输出结果如下: 自定义gradle文件的导入方法 上面所阐述的三个 gradle 文件是由系统来管理的,那我们能创建gradle文件吗?答案是肯定的. 那我们创建的 gra

  • Android Gradle 插件自定义Plugin实现注意事项

    目录 Android Gradle Plugin Gradle 是什么? Gradle 插件 分类 使用插件 实现一个插件 发布插件 引用插件 Android Gradle Plugin 在 Android 项目中的 build.gradle 文件中,经常可以看见一些 plugin 声明: plugins { id 'com.android.application' id 'com.android.library' } // or apply plugin: 'com.android.appli

  • Android Gradle同步优化详解

    目录 动态修改gradle配置 hook agp ProjectsServices 方法签名检查是否存在support包 年初开始我们就开始了关于Gradle Sync阶段的优化.之前和大家都简单的介绍过工程相关的背景情况了,我们大概有400+的Module,然后一次的同步时间就非常的慢,我们迫切的需要对这个问题进行优化.大部分工作都是和团队内的同学一起完成的,我也只出了一点点力而已. 这次写文章真的很倒霉,之前忘了保存导致要重新开始写了.如果不是白嫖了掘金的端午礼盒,拿人手短啊,我已经打算鸽了

  • Android开发之Gradle 进阶Tasks深入了解

    目录 前言 定义Task register与create的区别 查找Task 配置Task 将参数传递给Task构造函数 Task添加依赖 Task排序 Task添加说明 跳过Task 使用onlyIf 使用 StopExecutionException 禁用与启用Task Task超时 Task支持增量编译 Task的输入输出 自定义task类型 声明输入输出的好处 推断task依赖关系 输入和输出验证 并行task 增量编译原理解析 一些高端操作 将@OutputDirectory链接到@I

  • Android开发之FloatingActionButton悬浮按钮基本使用、字体、颜色用法示例

    本文实例讲述了Android开发之FloatingActionButton悬浮按钮基本使用.字体.颜色用法.分享给大家供大家参考,具体如下: 这里主要讲: FloatingActionsMenu自定义样式以及title调整 FloatingActionButton的基本方法 看一下效果图: 这里使用的是:com.getbase.floatingactionbutton.FloatingActionsMenu 先说下它的配置:在app/build.gradle 添加以下代码依赖: 圆形悬浮按钮 i

  • Android开发之AAR文件的生成与使用步骤

    目录 前言 一.AAR是什么? 二.使用步骤 1.生成AAR 2.AAR使用 附:注意事项 总结 前言 现在App开发组件化技术已是常态,有很多的功能模块都被抽出来成为一个个组件供给开发者使用.为了开发者使用,这些组件都会被打包,就和java中的库一样.在java中,一个模块可以被打包为Jar包,而在Android中,不仅仅有java文件,还有一些其他的资源文件,所以就出现了AAR文件(一种打包格式),本文通过一个demo来介绍如何打包一个Library文件,生成AAR. 一.AAR是什么? a

  • Android开发之TextView控件用法实例总结

    本文实例总结了Android开发之TextView控件用法.分享给大家供大家参考,具体如下: TextView控件可以向用户展现文本信息,我们可以设置该文本信息是否能编辑 1.TextView基本使用 在程序中创建TextView对象 在xml文件中布局使用 2.New Android Project-> Project name:TextView Build Target:Android 2.2 Application name:TextViewDemo Package name:com.b5

  • Android开发之Animations动画用法实例详解

    本文实例讲述了Android开发之Animations动画用法.分享给大家供大家参考,具体如下: 一.动画类型 Android的animation由四种类型组成:alpha.scale.translate.rotate XML配置文件中 alpha 渐变透明度动画效果 scale 渐变尺寸伸缩动画效果 translate 画面转换位置移动动画效果 rotate 画面转移旋转动画效果 Java Code代码中 AlphaAnimation 渐变透明度动画效果 ScaleAnimation 渐变尺寸

  • Android开发之ClipboardManager剪贴板功能示例

    本文实例讲述了Android开发之ClipboardManager剪贴板功能.分享给大家供大家参考,具体如下: 在开发一些系统应用的时候,我们可以需要用到Android的剪贴板功能,比如将文本复制到剪贴板或者从剪贴板复制数据等操作.使用起来很简单,系统给我们提供了很方便的接口,如下所示: //获取剪贴板管理服务 ClipboardManager cm =(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);

  • Android开发之sqlite3命令行简单使用方法

    本文实例讲述了Android开发之sqlite3命令行简单使用方法.分享给大家供大家参考,具体如下: 首先需要定位到database所在的目录里面,然后使用命令 sqlite3 databasename(数据库的名字)进入 常用命令: 1. .table     列取该数据库下面的数据表名 2. .schema 或者 .schema + 表名       获取整个或者是单个表的表结构 3. .database 显示该数据库所在的位置 4. .dump 显示数据库的所有信息(包括表结构,表数据)

  • Android开发之App widget用法实例分析

    本文实例讲述了Android开发之App widget用法.分享给大家供大家参考,具体如下: 放在桌面上的控件叫做--App widget,例如可以在桌面上添加按钮.图片等等控件,例如桌面播放器的控制面板 AppWidgetProviderInfo对象,它为App Widget提供元数据,包括布局.更新频率等等数据,这个对象不是由我们自己生成的,而是由android自己定义配置完成,这个对象被定义在XML文件中 1.定义AppWidgetProviderInfo对象,在res/xml文件夹当中定

  • Android开发之APP安装后在桌面上不显示应用图标的解决方法

    本文实例讲述了Android开发之APP安装后在桌面上不显示应用图标的解决方法.分享给大家供大家参考,具体如下: 一.问题: 前几天在写项目的时候运行的时候突然Android桌面上没有了应用图标,但是应用里面下载的应用有.调试版本和发布正式的版本都没有,之前以为是因为用了不同的keystore发布了两个不同的正式版本造成的问题.后来在看别人的文章才知道是什么问题. 二.分析: 原因就是activity中的intent-filter用了不同的data和action属性.intent-filter必

  • Android开发之CheckBox的简单使用与监听功能示例

    本文实例讲述了Android开发之CheckBox的简单使用与监听功能.分享给大家供大家参考,具体如下: activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_

随机推荐