Gradle 相關(guān)總結(jié)
APT 和 AGPTransform 區(qū)別
Gradle+Transform+Asm自動化注入代碼
Android 360加固+Walle多渠道自動化打包上傳蒲公英
gradle 是什么?
官方解釋:Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy or Kotlin DSL
使用純Java語言編寫的臣樱,基于Ant巩搏、Maven概念開源的自動化構(gòu)建工具,專注于靈活性和性能的構(gòu)建腳本,摒棄了基于XML的繁瑣配置,采用 Groovy 或 Kotlin 的 特定領(lǐng)域語言(DSL) 來編寫。
Gradle 作為一個自動化構(gòu)建工具愕难,Gradle 是通過組織一系列 Task 來最終完成自動化構(gòu)建的,所以 Task 是 Gradle 中最重要的概念惫霸,Gradle保證這些Task按照其依賴關(guān)系的順序執(zhí)行猫缭,并且每個任務(wù)僅執(zhí)行一次,這些任務(wù)形成有向無環(huán)圖壹店,在執(zhí)行任何Task之前猜丹,Gradle會構(gòu)建完整的依賴關(guān)系圖。比如以Android開發(fā)構(gòu)建 APK 為例硅卢,整個過程要經(jīng)過 資源的處理射窒,javac 編譯藏杖,dex 打包,簽名等等步驟脉顿,而這一系列步驟就對應(yīng)到 Gradle 里的Task蝌麸,Gradle 可以類比做一條流水線,而Task 就好比作流水線上的工人弊予,而每個工人負(fù)責(zé)自己不同的事情祥楣,經(jīng)過每個人處理最終生成完整的構(gòu)建產(chǎn)物开财,如果對責(zé)任鏈模式了解就知道這個概念有點類似責(zé)任鏈模式汉柒。Gradle文檔
Gradle基礎(chǔ)
基本概念
開始學(xué)習(xí)Gradle前,我們應(yīng)該先大概了解關(guān)于Gradle中的一些基礎(chǔ)概念:
- Gradle腳本是配置腳本责鳍。在腳本執(zhí)行時碾褂,它將配置特定類型的對象。例如历葛,在執(zhí)行構(gòu)建腳本時正塌,它將配置類型為Project的對象。該對象稱為腳本的委托對象恤溶,比如:在腳本執(zhí)行時會為build.gradle 生成對應(yīng)的Project對象乓诽。下表展示了每種Gradle腳本的委托對象:
Type of script | Delegates to instance of |
---|---|
Build script (build.gradle) | Project |
Init script | Gradle |
Settings script(settings.gradle) | Settings |
你可以在腳本中使用委托對象的屬性和方法。
-
每個Gradle腳本都實現(xiàn)Script接口咒程。該接口定義了可以在腳本中使用的許多屬性和方法,如下圖:
1. Gradle構(gòu)建生命周期 Build Lifecycle
1.1初始化階段(Initialization):
Gradle支持單項目和多項目構(gòu)建鸠天。在初始化階段,Gradle確定將要參與構(gòu)建的項目帐姻,并為每個項目創(chuàng)建一個Project實例稠集。
Init Script 讀取全局腳本,初始化一些全局通用屬性饥瓷,如:獲取Gradle User Home 目錄(新建init.gradle腳本打印信息)剥纷、Gradle Home(新建*.gradle腳本打印信息)、Gradle 版本信息呢铆;
Settting Script 初始化一次構(gòu)建所參與的所有模塊晦鞋,對應(yīng)setting.gradle腳本,組織管理項目中所有模塊的腳本棺克,參加構(gòu)建的腳本都需要在此腳本中進(jìn)行配置申明悠垛;
初始化階段主要做的事情是讀取setting.gradle腳本中配置的模塊,有哪些模塊需要參與本次的構(gòu)建逆航,然后為對應(yīng)的模塊創(chuàng)建 Project 對象鼎文,settings.gradle 是負(fù)責(zé)配置項目的腳本對應(yīng) Settings 類,gradle 構(gòu)建過程中因俐,會根據(jù) settings.gradle 生成 Settings 的對象拇惋;
1.2配置階段(Configuration):
開始加載所有在setting.gradle配置聲明的所有模塊Build Script即執(zhí)行build.gradle語句周偎,根據(jù)build.gradle腳本配置對應(yīng)模塊的Project對象,并創(chuàng)建模塊中對應(yīng)Task撑帖,最終根據(jù)所有的Task生成依賴圖(DAG有向無環(huán)圖)蓉坎;
配置階段主要做的事情是對Initialization階段創(chuàng)建的Project對象進(jìn)行配置,這時候會執(zhí)行對應(yīng)模塊的build.gradle 腳本胡嘿,并且會生成要執(zhí)行的 Task蛉艾;
1.3執(zhí)行階段(Execution):
*執(zhí)行階段主要做的事情就是執(zhí)行 Task,進(jìn)行主要的構(gòu)建工作衷敌;
小結(jié):
在Initialization階段首先會生成一個全局唯一的Gradle對象和Settings對象勿侯,其次是根據(jù)setting.gradle腳本中的配置信息為不同模塊生成Project對象。
Configuration階段主要是配置或初始化或配置Initialization階段生成的Project對象和生成Project對應(yīng)的Task依賴圖缴罗。
在Gradle腳本中助琐,setting.gradle、build.gradle它們是配置腳本面氓,腳本在執(zhí)行時兵钮,實際上是生成或配置了一個特殊類型的對象,比如:Init Script → Gradle對象舌界、Settings Script → Settings對象掘譬、Build Script → Project對象;這些對象又稱腳本的代理對象呻拌,
代理對象上的每個屬性葱轩、方法都可以在腳本中使用
。每個Gradle腳本都實現(xiàn)了Script接口柏锄,由0個或多個腳本語句 (statements)和腳本塊組成酿箭。 腳本語句可以包含:函數(shù)調(diào)用、屬性賦值和本地變量定義缭嫡,腳本塊則是一個方法調(diào)用,傳入一個 配置閉包抬闷,執(zhí)行時對代理對象進(jìn)行配置妇蛀。
Initialization(初始化階段)
1.Init Script(初始化腳本)
涉及到的腳本執(zhí)行順序如下:
GRADLE_USER_HOME/init.gradle → GRADLE_USER_HOME/init.d/*.gradle
這一步生成 Gradle 對象,并且會優(yōu)先執(zhí)行GRADLE_USER_HOME/init.gradle
腳本和GRADLE_USER_HOME/init.d/*.gradle
腳本,如果存在的話笤成,下面是Gradle類部分源碼如下:
public interface Gradle extends PluginAware {
String getGradleVersion();//執(zhí)行此次構(gòu)建的Gradle版本评架;
File getGradleUserHomeDir();//Gradle User Home目錄;
File getGradleHomeDir();//執(zhí)行此次構(gòu)建的Gradle目錄炕泳;
Project getRootProject()// 獲取當(dāng)前構(gòu)建的根項目
StartParameter getStartParameter();//獲取傳入當(dāng)前構(gòu)建的所有參數(shù)
TaskExecutionGraph getTaskGraph();//獲取當(dāng)前構(gòu)建的task graph纵诞,此對象在taskGraph.whenReady { } 后才具有內(nèi)容
}
Gradle父類 PluginAware 源碼 :
public interface PluginAware {
PluginContainer getPlugins();
void apply(Closure closure);
void apply(Action<? super ObjectConfigurationAction> action);
void apply(Map<String, ?> options);
PluginManager getPluginManager();
}
2. Settings Script(設(shè)置腳本)
settings.gradle腳本文件由Gradle通過命名約定確定。該文件的默認(rèn)名稱是settings.gradle培遵,settings.gradle腳本文件在初始化階段執(zhí)行浙芙。多項目構(gòu)建必須在多項目層次結(jié)構(gòu)的根項目中具有settings.gradle文件登刺。這是必需的,因為settings.gradle腳本文件定義了哪些項目正在參與多項目構(gòu)建嗡呼。對于單項目構(gòu)建纸俭,設(shè)置文件是可選的。
-
settings.gradle負(fù)責(zé)配置聲明參與構(gòu)建過程的模塊南窗,對應(yīng) Settings 類揍很,Gradle 構(gòu)建過程中,Gradle會根據(jù) settings.gradle腳本生成 Settings 的對象万伤。Settings部分源碼:
public interface Settings extends PluginAware, ExtensionAware { String DEFAULT_SETTINGS_FILE = "settings.gradle"; void include(String... projectPaths); void includeFlat(String... projectNames); ProjectDescriptor project(File projectDir) throws UnknownProjectException; }
Settings類中窒悔,比較常用的方法就是 include(String… projectPaths) 方法,用于添加參與構(gòu)建的模塊壕翩,ProjectDescriptor project(File projectDir) 方法也是比較常用蛉迹,當(dāng)子模塊不和settings.gradle腳本同一個目錄時需使用相對路徑描述,比如: project(':user').projectDir = File(rootDir, "user-module")放妈,user模塊的目錄只向根目錄先的user-module目錄。
-
前面有提到過Settings類實現(xiàn)PluginAware(插件相關(guān)接口)荐操,所以可以通過 Settings.pluginManagement方法的配置插件構(gòu)建過程中需要的插件倉庫:
pluginManagement { // 對應(yīng)PluginManagementSpec類 repositories { // 管理Plugin Repository(倉庫) google { url "https://maven.aliyun.com/repository/gradle-plugin" } } resolutionStrategy { eachPlugin { // 替換模塊 if (requested.id.namespace == "com.github.plugin") { useModule("com.xxxxxx.plugin:${requested.version}") } // 統(tǒng)一插件版本 if (requested.id.id == "com.android.tools.build") { useVersion("4.1.2") } } } }
配置階段(Configuration)
1.Build Script(構(gòu)建腳本build.gradle腳本)
build.gradle用于配置模塊的構(gòu)建所需要信息芜抒,分為根目錄的 Root Build Script 和 子模塊的 Module Build Script。
1.1托启、Root Build Script
- 根目錄的 Root Build Script 宅倒,一般是對所有子模塊進(jìn)行全局的統(tǒng)一的配置,build.gradle 腳本負(fù)責(zé)整體項目的基本配置屯耸,對應(yīng)的是Project類拐迁,我們稱之為:rootProject;
- Gradle 在構(gòu)建的時候疗绣,會根據(jù)根目錄下的build.gradle腳本生成 Project 對象线召,所以在 build.gradle 里寫的 dsl,其實都是 Project 接口的一些方法多矮,Project具體的實現(xiàn)類是DefaultProject類缓淹。
1.2、Module Build Script
- 子模塊的 Module Build Script塔逃,對當(dāng)前子模塊進(jìn)行配置讯壶,同樣的對應(yīng)的是Project類,我們稱之為:childProject湾盗;子項目和根項目的配置基本是相似伏蚊,不過在子項目里有一個明顯的區(qū)別就是引用
apply plugin "com.android.application"
,而android
dsl 就是 application 插件的 extension格粪,關(guān)于 android plugin dsl 可以看 android-gradle-dsl躏吊;
1.2.1 插件引入
Gradle插件是什么肺孵,官方解釋:
Gradle at its core intentionally provides very little for real world automation. All of the useful features, like the ability to compile Java code, are added by plugins. Plugins add new tasks (e.g. JavaCompile), domain objects (e.g. SourceSet), conventions (e.g. Java source is located at
src/main/java
) as well as extending core objects and objects from other plugins.
- 簡單點說就是,Gradle自身并沒有提供多少真實可用的功能颜阐,它只是一個負(fù)責(zé)定義流程和規(guī)則的框架平窘,具體的功能都是由插件來完成的,比如:編譯Java 則引用用
java
插件凳怨,編譯Kotlin用kotlin
插件,而這些插件并不是Gradle中自帶的功能瑰艘,而是有第三方作者定義的插件,所以說Gradle只負(fù)責(zé)定義流程和規(guī)則的框架肤舞。
1.2.1 .1 插件類型
Gradle中有兩種通用的插件類型紫新,即二進(jìn)制插件和腳本插件。
- 二進(jìn)制插件: 通過實現(xiàn)Plugin接口以編程的方式編寫李剖;
- 腳本插件:使用Gradle DSL語言以聲明方式編寫芒率,通常存在于另一個腳本文件中的一段腳本代碼;
示例:
引用二進(jìn)制插件:
apply plugin: 'com.android.application'
應(yīng)用工程目錄下的腳本插件:
apply from: rootProject.file("script_plugin.gradle")
1.2.2 插件屬性配置和方法調(diào)用
如果項目中應(yīng)用了某個插件篙顺,就可以使用此插件提供的DSL進(jìn)行配置偶芍,以此干預(yù)模塊的構(gòu)建過程。以com.android.application
插件構(gòu)建為例:
// 引入android.application插件
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.github.gradle"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
sourceSets {
main {}
}
}
關(guān)于自定義插件請移步Gradle+Transform+Asm自動化注入代碼
Gradle 相關(guān)API介紹
由于Gradle類的方法屬性很多,Gradle API文檔德玫,我就挑幾個平時可能會常用的的方法說一說:
API | 描述 |
---|---|
void beforeProject(Closure closure) | 當(dāng)每個Project對象配置之前被調(diào)用匪蟀,包括根Project |
void afterProject(Closure closure) | 當(dāng)每個Project對象配置完畢之后被調(diào)用,包括根Project |
buildStarted(Closure closure) | 當(dāng)構(gòu)建開始時回調(diào)宰僧,這個一般是看不到打印的 |
settingsEvaluated(Closure closure) | 當(dāng)Settings對象配置完成時回調(diào) |
projectsLoaded | 在初始化階段中所有的Project對象創(chuàng)建完成時回調(diào) |
projectsEvaluated | 當(dāng)所有的Project配置完成時回調(diào) |
buildFinished | 當(dāng)構(gòu)建完成時回調(diào) |
addBuildListener | 添加構(gòu)建監(jiān)聽 |
getRootProject | 返回Root Project |
TaskExecutionGraph getTaskGraph() | 返回此次構(gòu)建TaskExecutionGraph材彪,此對象在taskGraph.whenReady { } 后才具有內(nèi)容,因為task還沒填充完畢琴儿。 |
String getGradleVersion() | 執(zhí)行此次構(gòu)建的Gradle版本 |
File getGradleUserHomeDir() | Gradle User Home目錄 |
File getGradleHomeDir() | 執(zhí)行此次構(gòu)建的Gradle目錄 |
Project getRootProject() | 獲取當(dāng)前構(gòu)建的根項目 |
StartParameter getStartParameter() | 獲取傳入當(dāng)前構(gòu)建的所有參數(shù) |
TaskExecutionGraph 相關(guān)API介紹
TaskExecutionGraph負(fù)責(zé)管理作為構(gòu)建部分的Task實例的執(zhí)行段化。 TaskExecutionGraph維護(hù)要執(zhí)行(或已執(zhí)行)的任務(wù)的執(zhí)行計劃,您可以從構(gòu)建文件中查詢該計劃造成。 您可以通過調(diào)用Gradle.getTaskGraph()訪問TaskExecutionGraph显熏。在構(gòu)建文件中,您可以使用gradle.taskGraph對其進(jìn)行訪問谜疤。 僅在配置了此次構(gòu)建中的所有Project之后佃延,才填充TaskExecutionGraph。在此之前是空的夷磕。當(dāng)填充Graph時履肃,可以使用whenReady或addTaskExecutionGraphListener方法接收通知。
API | 描述 |
---|---|
addTaskExecutionGraphListener | 向TaskExecutionGraph添加TaskExecutionGraphListener監(jiān)聽坐桩,在填充TaskExecutionGraph之后以及執(zhí)行任何任務(wù)之前調(diào)用此方法 |
whenReady(Action<TaskExecutionGraph> action) | 當(dāng)TaskExecutionGraph填充完成時被調(diào)用 |
addTaskExecutionListener | 向TaskExecutionGraph添加TaskExecutionListener監(jiān)聽 |
beforeTask(Action<Task> action) | 當(dāng)TaskExecutionGraph管理的某個任務(wù)執(zhí)行之前被調(diào)用 |
afterTask(Action<Task> action) | 當(dāng)TaskExecutionGraph管理的某個任務(wù)執(zhí)行完成之后被調(diào)用 |
hasTask(Task task) | 查詢TaskExecutionGraph是否存在該task |
getAllTasks | 獲取TaskExecutionGraph管理的所有的Task |
Set getDependencies(Task task) | 返回TaskExecutionGraph管理的與參數(shù)Task的依賴關(guān)系 |
Project相關(guān)API介紹
該接口是用于與構(gòu)建文件中的Gradle對象交互的主要API尺棋。可以通過Projec對象以編程方式訪問Gradle的所有功能。
Project本質(zhì)上是Task對象的集合
膘螟。
Project生命周期Lifecycle
Project對象和"build.gradle"文件之間存在一對一的關(guān)系成福。在構(gòu)建初始化期間,Gradle 為要參與構(gòu)建的每個模塊創(chuàng)建一個Project對象荆残,如下所示:
- 為構(gòu)建創(chuàng)建一個Settings實例奴艾。
- 根據(jù)"settings.gradle"腳本(如果存在)配置Setttings對象。
- 使用已配置的Settings對象創(chuàng)建Project實例的層次結(jié)構(gòu)内斯。
- 通過執(zhí)行"build.gradle"文件(如果存在)來配置對應(yīng)的Project對象蕴潦。這些Project對象以廣度順序進(jìn)行配置,因此在其子項目之前先對其進(jìn)行配置俘闯√栋可以通過調(diào)用EvaluationDependsOnChildren()或通過使用EvaluationDependsOn(String)添加顯式配置依賴項來覆蓋此順序。
這里僅僅只介紹常用的API真朗,詳細(xì)API請看-官方文檔
API | 描述 |
---|---|
void beforeEvaluate(Action <? super Project> action) | 在配置該Project之前立即調(diào)用方法此疹。 |
void afterEvaluate(Action <? super Project> action) | 在配置該Project之后立即調(diào)用方法。 |
getRootProject() | 獲取根Project |
getRootDir | 返回該Project根目錄遮婶。根目錄是根Project的Project目錄蝗碎。 |
getBuildDir | 返回該項目的構(gòu)建目錄,構(gòu)建目錄是構(gòu)建過程中build產(chǎn)物都放入到這個里面蹭睡,構(gòu)建目錄的默認(rèn)值為projectDir / build衍菱。 |
setBuildDir(File path) | 設(shè)置該Project的構(gòu)建目錄。構(gòu)建目錄是構(gòu)建過程中build產(chǎn)物都放入到這個里面肩豁。 |
getParent() | 獲取此Project的父Project |
getChildProjects | 獲取此Project的所有直接子Project |
setProperty(String name, Object value) | 為Project的屬性設(shè)置新值 |
getProject() | 返回當(dāng)前Project對象,可用于訪問當(dāng)前Project的屬性和方法 |
getAllprojects | 返回當(dāng)前Project清钥,以及子Project的集合 |
allprojects(Closure configureClosure) | 在配置階段祟昭,閉包中返回Project及其子Project。 |
getSubprojects | 返回當(dāng)前Project下的所有子Project |
subprojects(Closure configureClosure) | 返回當(dāng)前Project下的所有子Project到閉包中 |
Task task(String name) | 創(chuàng)建一個Task怖侦,添加到此Project |
getAllTasks(boolean recursive) | 如果recursive為true那么返回當(dāng)前Project和子Project的全部Task,如果為false只返回當(dāng)前Project的所有task |
getTasksByName(String name, boolean recursive) | 根據(jù)名字返回Task急凰,如果recursive為true那么返回當(dāng)前Project和子Project的Task疾忍,如果為false只返回當(dāng)前Project的task |
hasProperty(String propertyName) | 查看是否存在此屬性 |
getProperties() | 獲取所有屬性 |
dependencies(Closure configureClosure) | 為Project配置依賴項 |
buildscript(Closure configureClosure) | 為該Project配置構(gòu)建腳本classpath撇簿。 |
getTasks() | 返回此Project中所有的tasks |
WorkResult copy(Action<? super CopySpec> action) | 復(fù)制指定的文件. |
CopySpec copySpec(Action<? super CopySpec> action) | 創(chuàng)建一個CopySpec,以后可以將其用于復(fù)制文件或創(chuàng)建存檔。給定的操作用于在此方法返回CopySpec之前對其進(jìn)行配置。 |
delete<? super DeleteSpec> action) | 刪除指定的文件邪码。 |
Task task(String name, Closure configureClosure) | 使用給定名稱創(chuàng)建一個Task并將其添加到該Project中影钉。 |
Task task(Map<String,?) args,String name) | 使用給定名稱創(chuàng)建一個Task并將其添加到該項目中夺谁。可以將創(chuàng)建參數(shù)的Map傳遞給此方法岗照,以控制Task的創(chuàng)建方式 |
Task介紹
Project本質(zhì)上是Task對象的集合
迫吐。每個任務(wù)執(zhí)行一些基本工作,例如編譯類,運行單元測試或壓縮WAR文件馆蠕『鸲桑可以使用TaskContainer的create方法將任務(wù)添加到Project中替劈。還可以使用TaskContainer上的查找方法耿戚,例如:TaskCollection.getByName(String)方法查找現(xiàn)有Task湿故。
每個Task都會附屬一個Project,可以在TaskContainer上使用各種方法來創(chuàng)建和查找任務(wù)實例膜蛔。例如皂股,TaskContainer.create(String)使用給定名稱創(chuàng)建一個空任務(wù)。您還可以在構(gòu)建文件中使用task關(guān)鍵字:
task myTask
task myTask1 { Task task ->}
task myTask2(type: JavaCompile)
task myTask3(type: JavaCompile) { Task task -> }
-
Task由一系列Action對象組成洋机。執(zhí)行Task時,通過調(diào)用Action.execute(T)依次執(zhí)行每個Action。通過調(diào)用doFirst(Action)或doLast(Action)將操作添加到任務(wù)中启搂。
task myTaskAction {Task task-> task.doFirst(new Action<Task>() { @Override void execute(Task taskAction) { println("execute task>>>>>>>>>>>>>>>>操作") } }) }
-
Groovy閉包也可以用于提供Task 的Action硼控。當(dāng)執(zhí)行Action時,以Task做為參數(shù)調(diào)用閉包疑苫。通過調(diào)用doFirst(Closure)或doLast(Closure)將Action閉包添加到任務(wù)中熏版。
task myTaskClosure { Task task-> task.doFirst{ } task.doLast { println("execute task closure>>>>>>>>>>>>>>>>操作") } }
一個Task可能依賴于其他Task,或者可能被安排為始終在另一個Task之后運行捍掺。任務(wù)的依賴關(guān)系使用dependsOn(Object ...)或setDependsOn(Iterable)進(jìn)行控制撼短。
task myTaskDepends { Task task ->
task.doLast {
println("execute task: " + task.getName())
}
}
task myTaskDepends1 { Task task ->
task.dependsOn("myTaskDepends")
task.doLast {
println("execute task: " + task.getName())
}
}
task myTaskDepends2 { Task task ->
task.setDependsOn([myTaskDepends,"myTaskDepends1"])
task.doLast {
println("execute task: " + task.getName())
}
}
特點:
- 一個Task可能依賴于其他Task,或者可能被安排為始終在另一個Task之后運行挺勿,如:taskA dependsOn taskB 曲横,那么當(dāng)執(zhí)行taskA 就必須會優(yōu)先執(zhí)行taskB,不管是否同時執(zhí)行不瓶。但是如果只執(zhí)行taskB禾嫉,那么taskA并不會執(zhí)行。
task排序
在某些情況下, 我們希望能控制Task的執(zhí)行順序, 這種控制并不是像上面顯式地加入(dependsOn(Object ...)或setDependsOn(Iterable)進(jìn)行控制)依賴關(guān)系蚊丐。 最主要的區(qū)別是我們設(shè)定的排序規(guī)則不會影響那些要被執(zhí)行的任務(wù), 只是影響Task執(zhí)行的順序本身.
task myTaskX { Task task ->
task.doLast {
println("execute task: " + task.getName())
}
}
task myTaskY { Task task ->
task.doLast {
println("execute task: " + task.getName())
}
}
myTaskY.mustRunAfter(myTaskX)
gradlew myTaskY myTaskX -q 的輸出:
execute task: myTaskX
execute task: myTaskY
注意 B.mustRunAfter(A)
或者 B.shouldRunAfter(A)
并不影響任何Task間的執(zhí)行
熙参。tasks A
和 taskB
可以被獨立的執(zhí)行。排序規(guī)則
只有當(dāng) 2 個任務(wù)同時執(zhí)行
時才會生效麦备。