Gradle基础学习(六) 认识任务Task

理解Gradle中的任务

Gradle的构建过程基于任务(Task)的概念,而每个任务都可以包含一个或多个动作(Action)。

任务是构建中执行的一些独立的工作单元,例如编译类、创建JAR、生成Javadoc或将存档发布到仓库。

我们使用gradle taskName或gradlew taskName来执行任务,比如build:

$ ./gradlew build 

项目中所有可用任务都是来自Gradle插件和构建脚本。

比如在子项目构建脚本中创建的任务:

可用任务清单

我们通过运行tasks任务来列出项目所有可用任务。

$ ./gradlew tasks

让我们以一个非常基本的Gradle项目为例,该项目具有以下结构:

 settings.gradle.kts文件定义了根项目名称和app子项目:

rootProject.name = "HelloGradle"

include("app")

目前app子项目的构建文件暂时是一个空文件。

为了看app子项目中的任务,运行./gradlew :app:tasks 

结果中只能看到少量的辅助任务(Help Tasks),它们是Gradle核心提供的用于分析构建的任务。其他任务,如构建项目或编译代码的任务,则是由插件来添加的。

我们接下来在app构建脚本里添加application插件:

//app/build.gradle.kts

plugins {

    id("application")

}

application插件会添加一些生命周期任务。 现在再运行./gradlew app:tasks,我们看到多了一些和构建相关的任务,比如assemble 和 build任务:

任务结果

当Gradle执行一个任务时,它会在控制台中显示任务的结果标签。

这些标签描述了一个任务是否有要执行的动作,Gradle是否执行了这些动作。动作包括但不限于编译代码、压缩文件和发布存档。

(no label) or EXECUTED

任务执行了动作。

任务有动作,Gradle执行了它们。

任务没有动作,有一些依赖项,Gradle执行了一个或多个依赖项。

UP-TO-DATE

Gradle会检查任务的输入和输出,如果输入没有变化,并且输出已经存在且是最新的,那么任务就不会被执行,并标记为UP-TO-DATE。

任务有输出和输入,但都没有改变。

任务有动作,但是告诉Gradle它不会改变输出。

任务没有动作,有一些依赖项,但所有依赖都是UP-TO-DATE, SKIPPED 或 FROM-CACHE的。

任务没有动作,也没有任何依赖项。

FROM-CACHE

Gradle检测到任务的输出已经在构建缓存中时,会直接从缓存中加载,比如build cache。 

SKIPPED

任务不执行他的动作

任务被跳过可能是因为某个先决条件未满足,或者任务被明确地配置为跳过,比如使用命令行选项--exclude-task排除。

NO_SOURCE

任务不需要执行他的动作

任务有输入和输出,但没有源文件。

FROM-CACHE和UP-TO-DATE都是Gradle优化构建过程的手段,都有助于减少不必要的任务执行,提高构建速度。可能会弄不明白什么时候是UP-TO-DATE或者FROM-CACHE,在本文后面介绍缓存任务的时候,我们再做进一步说明。 

任务组group和描述description

任务组和描述用于组织和描述任务。

Groups

任务组被用来对任务进行分类,当运行./gradlew tasks时,所有任务会被列在各自的组中,这样更容易理解它们的目的和与其他任务的关系,使用group属性设置组。

Descriptions
描述提供了任务功能的简要解释。当运行./gradlew tasks时,描述会显示在每个任务的旁边,帮助开发者了解它的用途以及如何使用它,使用description属性设置描述。

我们回头去看一下前面gradle-project执行tasks的输出结果:

:run任务属于Application分组, 对其描述是 "Runs this project as a JVM application"。 

这个任务在代码中的定义会像这样:

//app/build.gradle.kts

tasks.register("run") {

    group = "Application"

    description = "Runs this project as a JVM application."

}

私有和隐藏任务

Gradle不支持将任务标记为“私有”。

当我们运行:tasks时,默认只会显示那些分配了任务组的任务,即所谓的可见任务,而那些没有分配group的任务,就是隐藏任务, 需要注意,隐藏任务依旧可以被Gradle执行,只是不显示而已。

如下所示,我们创建了一个任务helloTask,执行./gradlew :app:tasks,任务列表里并没有找到helloTask任务。

//app/build.gradle.kts

tasks.register("helloTask") {

    println("Hello")

}

给它分配一个group 

tasks.register("helloTask") {

    group = "Other"

    description = "Hello task"

    println("Hello")

}

任务就出现在了指定的Other分组下 :

执行./gradlew tasks --all 可以显示所有任务,包括隐藏的。 

比如上面的helloHiddenTask,我们没有设置group属性,也显示在了Other分组下。 

分组任务

如果我们想要自定义执行tasks时向用户显示哪些任务,可以对任务分组并设置每个组的可见性。

示例gradle-project虽然只是一个简单的Java应用,列出的可用的任务却非常多,使用构建的开发人员很少直接需要其中的许多任务。

我们可以通过配置tasks任务,限制任务显示到一个特定的分组。

我们修改一下构建脚本,创建一个自己的分组,使用displayGroup属性来指定要显示的任务组。

//app/build.gradle.kts

val myBuildGroup = "my app build"               // Create a group name

tasks.register<TaskReportTask>("tasksAll") {    // Register the tasksAll task

    group = myBuildGroup

    description = "Show additional tasks."

    setShowDetail(true)

    println("register tasksAll")

}

tasks.named<TaskReportTask>("tasks") { 

    displayGroup = myBuildGroup

}

在Gradle中,我们执行tasks任务时,会使用此类型的一个实例TaskReportTask,其中displayGroup属性用来控制要显示的任务组,默认值是null, 可以使用命令行选项 '--group'设置,设置后就只显示这个分组的任务。

任务类别 

Gradle中的任务分为两类:

1. Actionable tasks(可操作任务)

2. Lifecycle tasks(生命周期任务)

可执行任务定义了Gradle应该执行的具体操作。例如,:compileJava 任务,它编译项目的Java代码。这些操作包括创建JAR文件、压缩文件、发布归档文件等。当你应用了一个插件,如java-library,Gradle会自动添加与该插件相关的可执行任务。

生命周期任务定义了一系列的目标(targets),你可以调用这些目标来执行一系列的操作。例如,:build 就是一个常见的生命周期任务,用于构建整个项目。这类任务本身不执行具体的操作(actions)。相反,它们捆绑了可执行任务,当调用生命周期任务时,会触发与之关联的可执行任务。 基础Gradle插件(base Gradle plugin)只添加生命周期任务。这意味着如果你没有添加任何插件,Gradle仍然会提供这些基本的生命周期任务。

我们再看一下之前例子的:tasks结果

如果我们执行:build任务,会看到有好几个任务都被执行了,包括:app:compileJava任务。

可以表述为可执行任务:compileJava捆绑到了生命周期任务:build中。 

增量任务

Gradle任务的一个关键特征是它们的增量性。

Gradle可以重用之前构建的结果。因此,如果我们之前构建过项目,并且只进行了小幅更改,那么重新运行:build将不需要Gradle执行大量工作。

例如,如果我们只修改项目中的测试代码,保持生产代码不变,执行构建将仅重新编译测试代码。Gradle将生产代码的任务标记为UP-TO-DATE,表明自上次成功构建以来,它保持不变:

缓存任务

Gradle可以使用构建缓存来重用过去构建的结果。

要启用此功能,请使用--build-cache命令行参数或在gradle.properties文件中设置org.gradle.caching=true来激活构建缓存。

此优化有可能显著加速项目的构建:

当Gradle可以从缓存中获取一个任务的输出时,它会给这个任务贴上FROM-CACHE的标签。如果经常在分支之间切换,构建缓存很方便。Gradle支持本地和远程构建缓存。

我们通过实际执行其中的compileJava源码编译任务来加深对任务结果为UP-TO-DATE和FROM-CACHE的理解:

我们把之前的gradle-project例子修改一下

//app/build.gradle.kts

//添加执行入口类

application {

    mainClass = "gradle.project.App"

}

//app/src/main/java/gradle/project/App.java

package gradle.project;

public class App {

    public static void main(String[] args) {

        System.out.println("Hello World!");

   }

}

现在app子项目是一个可执行的Java 应用程序,App类是应用的执行入口。

先./gradlew clean删除各子项目build目录,确保项目是干净的状态。

执行compileJava任务编译java,  --build-cache告诉Gradle本次使用构建缓存,需要--info选项显示一些额外的构建信息:

./gradlew --build-cache compileJava --info

Settings evaluated using settings file '/Users/roy/Downloads/gradle-project/settings.gradle'.

Using local directory build cache for the root build (location = /Users/xxx/.gradle/caches/build-cache-1, removeUnusedEntriesAfter = 7 days).

Projects loaded. Root project using build file '/Users/xxx/Downloads/gradle-project/build.gradle'.

> Task :app:compileJava

Stored cache entry for task ':app:compileJava' with cache key db35214e1886f8b0ebbcc16e2fa7a618

BUILD SUCCESSFUL in 1s

1 actionable task: 1 executed

 执行成功后,app子项目里多了build目录,输出的信息里也看到了build cache目录默认的位置Gradle User Home/caches/build-cache-1/,在其中缓存了任务的输出。

再次执行

./gradlew --build-cache compileJava --info

> Task :app:compileJava UP-TO-DATE

Build cache key for task ':app:compileJava' is 3804aa4dacefba7c96c077f8de82ae3d

Skipping task ':app:compileJava' as it is up-to-date.

BUILD SUCCESSFUL in 1s

1 actionable task: 1 up-to-date

因为我们没有做任何改动,build目录里已经最新的输出了,所以compileJava任务会跳过,此时任务被标记为UP-TO-DATE。

执行./gradlew clean删除app的build目录,然后再执行

./gradlew --build-cache compileJava --info 

> Task :app:compileJava FROM-CACHE

Build cache key for task ':app:compileJava' is db35214e1886f8b0ebbcc16e2fa7a618

Task ':app:compileJava' is not up-to-date because:

  Output property 'destinationDirectory' file /Users/xx/Downloads/gradle-project/app/build/classes/java/main has been removed.

  Output property 'destinationDirectory' file /Users/xx/Downloads/gradle-project/app/build/classes/java/main/gradle has been removed.

  Output property 'destinationDirectory' file /Users/xx/Downloads/gradle-project/app/build/classes/java/main/gradle/project has been removed.

  Output property 'options.generatedSourceOutputDirectory' file /Users/xx/Downloads/gradle-project/app/build/generated/sources/annotationProcessor/java/main has been removed.

Loaded cache entry for task ':app:compileJava' with cache key db35214e1886f8b0ebbcc16e2fa7a618

BUILD SUCCESSFUL in 1s

1 actionable task: 1 from cache

任务标记为FROM-CACHE,并且从缓存中加载了上次执行的输出,日志中也有提示:

Loaded cache entry for task ':app:compileJava' with cache key db35214e1886f8b0ebbcc16e2fa7a618

 另外,app的build目录也有了,里面的输出应该就是直接从build cache中拿过来的。

 再再一次执行./gradlew --build-cache compileJava --info, 任务又是UP-TO-DATE了。

> Task :app:compileJava UP-TO-DATE

所以,对于要执行的任务,只要项目里已经存在最新输出,它就是UP-TO-DATE;否则如果任务启用构建缓存,并且在缓存里有最新输出,就是FROM-CACHE。 

可缓存任务和不可缓存任务

在Gradle中并非所有任务都可以或应该被缓存,除了少数内置任务是可缓存外,大部分任务由于各种原因(如不可预测的输出、外部依赖、任务配置等)可能不适合缓存。这些任务通常被标记为non-cacheable。

使用--build-cache选项可以让Gradle启用构建缓存功能。当这个选项被启用时,Gradle会尝试缓存可缓存任务的输出,并在后续构建中重用这些输出。

对于non-cacheable任务,Gradle会忽略构建缓存机制,并总是执行这些任务。通常是因为这些任务的输出可能依赖于不可预测或不可重复的因素,或者任务本身的配置不允许缓存。

开发者也可以显式地配置这类任务使其可缓存。

要确定一个任务是否可缓存,你可以查看任务的输出。如果任务有一个buildCacheable属性,并且它被设置为true,那么该任务就是可缓存的。如果任务没有明确的buildCacheable属性设置,或者它被设置为false,那么该任务就是non-cacheable的。

构建缓存的有效性还取决于构建环境的稳定性和一致性。如果构建环境经常变化(例如,使用了不同的构建机器或文件系统),那么构建缓存的效果可能会受到限制。因此,在使用构建缓存时,确保构建环境的一致性是非常重要的。

开发任务

在开发Gradle任务时,我们有两个选择:

1.使用现有的Gradle任务类型,比如Zip,Copy或Delete

2.创建自己的任务类型,比如MyResolveTask或者CustomTaskUsingToolchains

任务类型就是是Gradle Task类的子类。

对于Gradle任务,有三种状态需要考虑:

1.注册任务 - 在构建逻辑中使用一个任务(由您实现或由Gradle提供)。

2.配置任务 - 定义任务的输入和输出。

3.实现任务 - 创建一个自定义任务类(即自定义类型)

注册通常使用register()方法完成。

配置任务通常使用named()方法完成。

实现任务通常是通过扩展Gradle的DefaultTask类来完成的:

①: 注册Copy类型的myCopy任务。

②: 根据Copy API为注册的myCopy任务配置它所需的输入和输出。

③: 实现一个名为MyCopyTask的自定义任务类型,它扩展了DefaultTask并定义了copyFiles任务操作。

1.注册任务

我们可以通过在构建脚本和插件中注册任务来定义Gradle要执行的操作。

使用字符串作为任务名来定义任务:

//build.gradle.kts

tasks.register("myCopy") {

   doFirst {

        // Task action = Execution code

        // Run before exiting actions

    }

    doLast {

        // Task action = Execution code

        // Run after existing actions

    }

}

register()方法会将myCopy任务添加到TaskCollection中。

2.配置任务

Gradle任务必须经过配置来完成他们的操作。比如一个任务需要ZIP一个文件,我们就必须要配置文件名和文件位置。这可以参考Gradle Zip任务提供的API,学习如何正确进行配置。

在上图示例中,我们注册了一个myCopy任务

tasks.register<Copy>("myCopy")

我们可以在注册的时候就立即用代码块配置任务:

tasks.register<Copy>("myCopy") {

   from("resources")

   into("target")

   include("**/*.txt", "**/*.xml", "**/*.properties")

}

因为这个任务是Copy类型, 是Gradle支持的任务类型,所以可以使用Copy API,如from、to。

之后在需要的地方,都可以通过named()方法查找对应名字的任务来配置:

//build.gradle.kts

tasks.named<Copy>("myCopy") {

    from("resources")

    into("target")

    include("**/*.txt", "**/*.xml", "**/*.properties")

}

 注意,在named()调用时,如果指定的任务还没有注册,就会构建失败。

3.实现任务

Gradle提供了很多任务类型,包括 Delete, Javadoc, Copy, Exec, Tar和Pmd等等,如果都满足不了我们的构建逻辑需求,我们可以实现一个自定义的任务类型。

要创建一个任务类型,需要扩展DefaultTask,然后定义为一个抽象类(不用是具体实现类):

//app/build.gradle.kts
abstract class MyCopyTask extends DefaultTask {}

Gradle 会通过解析 build.gradle 文件中的配置动态创建任务实例,这些实例是任务类的具体实现,它们包含了在配置中设置的所有参数和指定的动作。

task("taskName")与tasks.register("taskName")

在 Gradle 中,task("taskName") 和 tasks.register("taskName") 都被用来创建新的任务,但它们属于不同的 API 并具有一些细微的差别和用法上的考虑。

传统 DSL(领域特定语言)方式:

task("taskName") 是 Gradle 的传统 DSL(领域特定语言)方法,用于在 build.gradle 文件中声明一个任务。这种方式相对直观和简单,适用于简单的任务定义。

任务注册(Tasks API)方式:

tasks.register("taskName") 是 Gradle Tasks API 的一部分,用于以编程方式注册任务。它提供了更多的灵活性和控制,尤其是在需要基于其他任务或项目配置动态创建任务时。

主要区别:传统 DSL 方法在配置阶段就执行了任务的配置代码,而 Tasks API 则允许延迟配置,直到执行阶段才执行配置代码。这有助于避免在配置阶段发生不必要的副作用。

随着 Gradle 的不断进化,Tasks API 被认为是更现代和推荐的方式来创建和注册任务。

tasks.register("taskName") 实现延迟配置的原理主要基于 Gradle 的任务生命周期和任务注册机制。

在 Gradle 构建的生命周期中,任务(Task)的创建和配置是分开的。传统的 task("taskName") 语法在项目的配置阶段(configuration phase)就立即创建并配置了任务。这意味着,即使在任务从未被执行的情况下,其配置代码也会被执行。这有时可能会导致不必要的副作用,比如提前计算了某些值,或者执行了只在任务执行时才需要的逻辑。

相比之下,tasks.register("taskName") 使用了一种不同的方法。这个方法实际上并没有立即创建任务,而是注册了一个任务工厂(TaskFactory)。这个工厂会在任务首次执行时(execution phase)被调用,从而创建任务实例并执行其配置,这就是所谓的“延迟配置”(lazy configuration)。

具体来说,当你调用 tasks.register("taskName") 时,Gradle 会创建一个 TaskRegistration 对象,该对象封装了任务的配置逻辑(即你传递给 register 方法的闭包)。这个 TaskRegistration 对象会被添加到 Gradle 的任务容器中,但不会立即创建任务实例。

当 Gradle 执行阶段到来,并且需要执行名为 "taskName" 的任务时,Gradle 会从任务容器中检索相应的 TaskRegistration 对象,并调用其工厂方法来创建任务实例。此时,闭包中的配置逻辑才会被执行,从而配置新创建的任务实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/10864.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4.5网安学习第四阶段第五周回顾(个人学习记录使用)

本周重点 ①部署域环境&#xff08;Win2008&#xff09; ②域组策略 ③域内信息收集 ④(重点)哈希传递攻击PTH ⑤MS14-068 提权漏洞 ⑥黄金票据伪造 ⑦白银票据伪造 ⑧ZeroLogon (CVE-2020-1472) 漏洞复现 本周主要内容 ①部署域环境&#xff08;Win2008&#xff09;…

【算法】滑动窗口——串联所有单词的子串

今天来以“滑动窗口”的思想来详解一道比较困难的题目——串联所有单词的子串&#xff0c;有需要借鉴即可。 目录 1.题目2.下面是示例代码3.总结 1.题目 题目链接&#xff1a;LINK 这道题如果把每个字符串看成一个字母&#xff0c;就是另外一道中等难度的题目&#xff0c;即&…

不同路径| 和 不同路径||

不同路径| 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xf…

大学生体质测试|基于Springboot+vue的大学生体质测试管理系统设计与实现(源码+数据库+文档)

大学生体质测试管理系统 目录 基于Springboot&#xff0b;vue的大学生体质测试管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3用户功能模块 4教师功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算…

C++笔试强训day20

目录 1.经此一役小红所向无敌 2.连续子数组最大和 3.非对称之美 1.经此一役小红所向无敌 链接 简单模拟即可。 需要注意的是&#xff1a; 除完之后有无余数&#xff0c;若有&#xff0c;则还可以再挨一次打。 #include <iostream> using namespace std; #define in…

设计模式——结构型模式——代理模式(静态代理、动态代理:JDK、CGLIB)

目录 代理模式 代理模式简介 代理模式的分类 代理模式组成 代理模式的优缺点 静态代理 背景前置 编写代码 JDK动态代理 编写代码 使用Arthas分析JDK动态代理底层原理 CGLIB动态代理 编写代码 三种代理的对比 代理模式使用场景 代理模式 代理模式简介 代理模式属…

Mybatis操作数据库的两种方式:Mapper代理模式

1.Mapper代理模式的特点 程序员没有写接口的子实现——直接获取数据库的数据 因为Mybatis定义了一套规则&#xff0c;对方法进行了实现&#xff0c;程序员只要遵循这套方法就可以直接使用 2.如何实现Mapper代理模式 步骤&#xff1a; 1.创建一个dao接口&#xff0c;在接口…

java项目之英语知识应用网站源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的英语知识应用网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 英语知识应用网站的主要…

【免费】AME最新Adobe Media Encoder电脑软件安装包2024-2018支持WIN和MAC

Adobe MediaEncoder「Me」2024是一款功能强大的转码和媒体处理软件&#xff0c;它不仅能轻松应对各种媒体文件的编码和导出需求&#xff0c;还支持多种视频格式和分辨率&#xff0c;让你的视频处理变得更加高效。此外&#xff0c;该软件界面简洁明了&#xff0c;操作简便&#…

【一步一步了解Java系列】:了解Java与C语言的运算符的“大同小异”

看到这句话的时候证明&#xff1a;此刻你我都在努力~ 加油陌生人~ 个人主页&#xff1a; Gu Gu Study ​​ 专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努…

【Element-UI快速入门】

文章目录 **Element-UI快速入门****一、Element-UI简介****二、安装Element-UI****三、引入Element-UI****四、使用Element-UI组件****五、自定义Element-UI组件样式****六、Element-UI布局组件****七、Element-UI表单组件****八、插槽&#xff08;Slots&#xff09;和主题定制…

【数据结构】排序(一)—— 希尔排序(思路演进版)

目录 一、常见的排序算法分类 二、常见排序算法的实现 2.1插入排序 2.1.1基本思想 2.1.2直接插入排序 思路 step1.单趟控制 step2.总体控制 代码实现 测试 特性总结 2.1.3 希尔排序( 缩小增量排序 ) 基本思想 思路演进 &#x1f308;1.代码实现单组排序&#…

端午节线上活动方案怎么写?

一年一端午&#xff0c;一岁一安康。 如果您想组织端午活动&#xff0c;却不知道如何安排&#xff0c;可以看看何策网&#xff0c;有很多案例参考&#xff0c;仿造模板修改即可。 下面分享一个线上端午节活动策划方案&#xff0c;希望能帮到你&#xff01; 端午节作为祭祖祈…

mysql集群NDBcluster引擎在写入数据时报错 (1114, “The table ‘ads‘ is full“)

问题描述&#xff1a;mysql集群在写入数据时&#xff0c;出现上述报错 问题原因&#xff1a;表数据已满&#xff0c;一般是在集群的管理节点设置里面datamemory的值太小&#xff0c;当数据量超过该值时就会出现该问题 解决方案&#xff1a; 修改集群管理节点的config.ini里面…

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习2

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习2 1、 for i in range(3):Dev.turnRight()for j in range(3):Dev.step(-3)Dev.turnRight()Dev.step(4-2*i)2、 for i in range(6):for j in range(2):Dev.step(2 2 * i)if i > 3: Dev.step(i - 2)Dev.turnRi…

C++小程序:同一路由器下两台计算机间简单通信(2/2)——客户端

客户端的程序结构前半部分与服务器端基本相同&#xff0c;后半部分也相对简单。相关函数的解释可以参考前文服务器端的内容。有关客户端的内容除个别地方外&#xff0c;就不再做长篇大论的解释。强调一点&#xff0c;如果将此程序移到其它电脑上运行&#xff0c;编译需要releas…

Ciphey无法安装的解决办法

安装过程纯属自己实践&#xff0c;满满干货 困扰我几天的问题终于解决了 我看着教程在window上安装 python3.8/python3.9/python3.10无论如何都安装不上&#xff0c; 在win10虚拟机仍然安装不上 可能是我电脑环境问题 解决办法&#xff1a; 在kali中安装&#xff0c;但是…

第13节 第二种shellcode编写实战(2)

在第二种shellcode编写实战(1)的基础上&#xff0c;新增加一个CAPI类&#xff0c;将所有用到的函数都在这个类中做动态调用的处理&#xff0c;这样使得整个shellcode功能结构更加清晰。 1. 新建类CAPI&#xff08;即api.h和api.cpp两个文件&#xff09;&#xff1a; api.h&…

flutter自定义日期选择器按日、按月、自定义开始、结束时间

导入包flutter_datetime_picker: 1.5.0 封装 import package:atui/jade/utils/JadeColors.dart; import package:flutter/cupertino.dart; import package:flutter/material.dart; import package:flutter_datetime_picker/flutter_datetime_picker.dart; import package:flut…

景源畅信电商:经营抖店需要电脑吗?

经营抖店是否需要电脑?这个问题看似简单&#xff0c;实则关乎着商家的运营效率和成本投入。在当前数字化、网络化的商业环境中&#xff0c;电脑已经成为了不可或缺的工具。那么&#xff0c;经营抖店究竟是否需要电脑呢?答案是肯定的。 一、高效处理订单 电脑能够高效地处理大…