從Android Plugin原始碼開始徹底理解gradle構建:Task(三)
*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出
系列文章:
一、前言回顧
首先我們依然回顧一下basePlugin裡的三個回撥:
//plugin的基礎設定、初始化工作
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
project.getPath(),
null,
this ::configureProject);
//EXTENSION的初始化工作
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
project.getPath(),
null,
this::configureExtension);
//plugin的task建立
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
project.getPath(),
null,
this::createTasks);
上一篇文中我們已經詳細介紹了第二步extension的用法和原始碼了,今天就來說說最後一步,也是gradle裡最重要內容之一的Task。
二、task介紹
task,如其名:任務,gradle就是由一個一個任務來完成的。他其實也是一個類,有自己的屬性,也可以”繼承”,甚至他還有自己的生命週期。
他的定義方式有很多,下面我們來看一個最簡單的實現:
task myTask {
println "myTask invoked!"
}
gradle就是一個一個task組成的,我們平時遇到莫名其妙的報錯,最常用的就是clean(斜眼笑),其實也是一個task而已,包括我們debug執行、打包簽名等等等等,都是Android studio給我們檢視化了而已,本質也是執行task。所以我們執行一下clean命令:gradle clean
myTask invoked!還是被打出來了,為啥?其實上面我也提到了,task有自己的生命週期。
初始化—配置期—執行期,我從實戰gradle裡偷了一張圖:
其實上面程式碼就是在配置階段而已,配置階段的程式碼只要在執行任何task都會跟著執行,如果我們希望不被執行的話,就只能放到執行階段了,最直接的方法就是加到doLast、doFirst裡,當然實現方式也挺多的,我就列兩種吧:
project.task('printPerson') {
group 'atom'
//定義時
doLast {
println "this is doLast1"
}
}
Task printPerson= project.tasks["printPerson"]
//後來加
printPerson.doFirst {
println "this is doFirst1"
}
printPerson.doFirst {
println "this is doFirst2"
}
printPerson.doLast {
println "this is doLast2"
}
剛開始可能不好理解這種方式,其實可以理解為task裡有一個佇列,佇列中是task將會執行的action,當doFirst 時,就會在佇列頭部插入一個action,而doLast則在佇列尾部新增,當執行該任務時就會從佇列中取出action依次執行,就如同我們上述程式碼,執行gradle printPerson時,列印結果如下:
> Task :app:printPerson
this is doFirst2
this is doFirst1
this is doLast1
this is doLast2
注意,此時必須要執行gradle printPerson時才會列印了,clean之流就沒用了。
剛剛提到過,task其實也是一個類,沒錯,就如同object一樣,task的基類是DefaultTask ,我們也可以自定義一個task,必須繼承DefaultTask,如下:
class MyTask extends DefaultTask {
String message = 'This is MyTask'
// @TaskAction 表示該Task要執行的動作,即在呼叫該Task時,hello()方法將被執行
@TaskAction
def hello(){
println "Hello gradle. $message"
}
}
其實task還有許多內容,比如輸入輸出檔案outputFile、Input
but,對於android開發目前來說,這就夠了,但是瞭解一下也是很有好處的,比如我們構建速度就和輸入輸出有關,是不是被這個坑爹的構建速度鬱悶到很多次~我推薦大家去看看《Android+Gradle權威指南》之類的書,目前網上資料不全不夠系統,當然,官方文件還是值得好好看的~
閒話少敘,繼續主題。task還有一個比較重要的內容,就是“繼承”。
沒錯,task之間也是可以‘繼承’的,不過此繼承非彼繼承,而是通過dependsOn關鍵字實現的,我們先來看看實現:
task task1 << {
println "this is task1"
}
task task2 << {
println "this is task2"
}
task task3 << {
println "this is task3"
}
task task4 << {
println "this is task4"
}
task1.dependsOn('task2')
task2.dependsOn('task3')
task1.dependsOn('task4')
‘繼承’關係為:task1–>task2/task4和task2–>task3
我們先打印出來:
> Task :app:task3
this is task3
> Task :app:task2
this is task2
> Task :app:task4
this is task4
> Task :app:task1
this is task1
可以看到,task3是最先執行的,這是因為dependsOn的邏輯就是首先執行‘最高’輩分的,最後執行‘最低’輩分的。什麼意思呢,拿程式碼來說就是task1‘繼承’了task2,task4,而task2‘繼承’了task3,意思就是task3是task1的爺爺輩,所以最先執行,這樣相信大家能夠理解了吧。
task基礎部分大概就講這麼多了吧,接下來我們終於可以分析原始碼了。
三、Android的assemble原始碼
assemble是一個task,用於構建、打包專案,平時我們打包簽名APK就是呼叫了該方法,由於我們有不同buildTypes,以及不同productFlavors,所以我們還需要生成各種不同的assemble系列方法:assemble{productFlavor}{BuildVariant},比如
assembleRelease:打所有的渠道Release包
assemblexiaomiRelease:打小米Release包
assemblehuaweiRelease:打華為Release包
AndroidDSL負責生成我們在build.gradle裡配置的多渠道等各種assemble系列方法。
然後assemble方法會依賴很多方法,就如同我們上文所敘述的,依次執行assemble依賴的方法完成構建,好了,我們還是來看原始碼理解吧!
文章開頭已經放出來原始碼,第三個註釋就是Android的建立task部分,我們直接看該方法:
private void createTasks() {
//建立一些解除安裝APK、檢查裝置等方法
threadRecorder.record(
ExecutionType.TASK_MANAGER_CREATE_TASKS,
project.getPath(),
null,
() -> taskManager.createTasksBeforeEvaluate());
//建立Android相關重要方法
project.afterEvaluate(
project ->
threadRecorder.record(
ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> createAndroidTasks(false)));
}
其實就是呼叫了createTasksBeforeEvaluate和createAndroidTasks兩個方法,註釋寫的很明白了,createAndroidTasks才是重點,該方法中又呼叫了variantManager的createAndroidTasks方法,跳過與本文無關的細節,看下面重要的地方:
/**
* Variant/Task creation entry point.
*/
public void createAndroidTasks() {
//省略部分程式碼...
for (final VariantScope variantScope : variantScopes) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
project.getPath(),
variantScope.getFullVariantName(),
() -> createTasksForVariantData(variantScope));
}
}
迴圈呼叫createTasksForVariantData方法,該方法就是為所有的渠道建立相關方法了,而variantScopes則存放了各種渠道、buildType資訊,繼續檢視該方法:
/** Create tasks for the specified variant. */
public void createTasksForVariantData(final VariantScope variantScope) {
//1======
final BaseVariantData variantData = variantScope.getVariantData();
final VariantType variantType = variantData.getType();
final GradleVariantConfiguration variantConfig = variantScope.getVariantConfiguration();
final BuildTypeData buildTypeData = buildTypes.get(variantConfig.getBuildType().getName());
if (buildTypeData.getAssembleTask() == null) {
//2======
buildTypeData.setAssembleTask(taskManager.createAssembleTask(buildTypeData));
}
// Add dependency of assemble task on assemble build type task.
//3======
taskManager
.getTaskFactory()
.configure(
"assemble",
task -> {
assert buildTypeData.getAssembleTask() != null;
task.dependsOn(buildTypeData.getAssembleTask().getName());
});
//4======
createAssembleTaskForVariantData(variantData);
if (variantType.isForTesting()) {
//省略測試相關程式碼...
} else {
//5======
taskManager.createTasksForVariantScope(variantScope);
}
}
1、解析variant渠道等資訊
2、建立AssembleTask存入data裡
3、給assemble新增依賴
4、建立該variant的專屬AssembleTask
5、給AssembleTask新增構建專案所需task依賴(dependsOn)
看一下4、5步驟詳細程式碼,首先是第四步,給每個渠道和buildtype建立對應的方法:
/** Create assemble task for VariantData. */
private void createAssembleTaskForVariantData(final BaseVariantData variantData) {
final VariantScope variantScope = variantData.getScope();
if (variantData.getType().isForTesting()) {
//測試
} else {
BuildTypeData buildTypeData =
buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName());
Preconditions.checkNotNull(buildTypeData.getAssembleTask());
if (productFlavors.isEmpty()) {
//如果沒有設定渠道
} else {
//省略部分程式碼...
// assembleTask for this flavor(dimension), created on demand if needed.
if (variantConfig.getProductFlavors().size() > 1) {
//獲取渠道名
final String name = StringHelper.capitalize(variantConfig.getFlavorName());
final String variantAssembleTaskName =
//組裝名字
StringHelper.appendCapitalized("assemble", name);
if (!taskManager.getTaskFactory().containsKey(variantAssembleTaskName)) {
//建立相應渠道方法
Task task = taskManager.getTaskFactory().create(variantAssembleTaskName);
task.setDescription("Assembles all builds for flavor combination: " + name);
task.setGroup("Build");
//渠道方法依賴AssembleTask
task.dependsOn(variantScope.getAssembleTask().getName());
}
taskManager
.getTaskFactory()
.configure(
"assemble", task1 -> task1.dependsOn(variantAssembleTaskName));
}
}
}
}
註釋已經很清晰了,最重要的就是組裝名字,建立相應的渠道打包方法。這裡我們又學到一種定義task的方式:TaskFactory.create
這是AndroidDSL自定義的類,他的實現類是TaskFactoryImpl,由kotlin語言實現:
class TaskFactoryImpl(private val taskContainer: TaskContainer): TaskFactory {
//....
override fun configure(name: String, configAction: Action<in Task>) {
val task = taskContainer.getByName(name)
configAction.execute(task)
}
override fun findByName(name: String): Task? {
return taskContainer.findByName(name)
}
override fun <T : Task> create(
taskName: String, taskClass: Class<T>, configAction: Action<T>): T {
return taskContainer.create(taskName, taskClass, configAction)
}
}
省略了大部分方法,但也很簡單了,使用代理模式代理了taskContainer,而這個taskContainer就是gradle的類了,檢視官方文件:
<T extends Task> T create(String name,
Class<T> type,
Action<? super T> configuration)
throws InvalidUserDataException
Creates a Task with the given name and type, configures it with the given action, and adds it to this container.
After the task is added, it is made available as a property of the project, so that you can reference the task by name in your build file. See here for more details.
Specified by:
create in interface PolymorphicDomainObjectContainer<Task>
Type Parameters:
T - the type of the domain object to be created
Parameters:
name - The name of the task to be created.
type - The type of task to create.
configuration - The action to configure the task with.
Returns:
The newly created task object.
Throws:
InvalidUserDataException - If a task with the given name already exists in this project.
就是建立一個task並放入容器裡
引數只有第三個比較難猜一點點,看了文件也就很清楚:給task設定一個action而已。當然,這裡並沒有呼叫這個過載方法,不過我這裡是為了第5步介紹,好的,讓我們回到第5步操作:
taskManager.createTasksForVariantScope(variantScope);
這裡taskManager由BasePlugin的子類實現,實現類為ApplicationTaskManager,我們看一下他的createTasksForVariantScope方法:
@Override
public void createTasksForVariantScope(@NonNull final VariantScope variantScope) {
BaseVariantData variantData = variantScope.getVariantData();
assert variantData instanceof ApplicationVariantData;
createAnchorTasks(variantScope);
createCheckManifestTask(variantScope);
handleMicroApp(variantScope);
// Create all current streams (dependencies mostly at this point)
createDependencyStreams(variantScope);
// Add a task to publish the applicationId.
createApplicationIdWriterTask(variantScope);
taskFactory.create(new MainApkListPersistence.ConfigAction(variantScope));
taskFactory.create(new BuildArtifactReportTask.ConfigAction(variantScope));
// Add a task to process the manifest(s)
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createMergeApkManifestsTask(variantScope));
// Add a task to create the res values
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createGenerateResValuesTask(variantScope));
// Add a task to merge the resource folders
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
project.getPath(),
variantScope.getFullVariantName(),
(Recorder.VoidBlock) () -> createMergeResourcesTask(variantScope, true));
//省略類似方法
}
這個方法就是構建精髓所在,他建立了我們構建專案所需要的大部分task,比如建立manifest檔案,合併manifest檔案,處理resource檔案…等等task,這些task就是構建專案的基石,這裡我就放出任玉剛大佬總結的主要構建方法:
具體每個方法做了什麼,就是需要大家閱讀原始碼參透了,這裡我只負責梳理大致流程,嘿嘿…
下面我們就看看建立的第一個方法createAnchorTasks,在這個方法裡面呼叫了createCompileAnchorTask,他的實現是:
private void createCompileAnchorTask(@NonNull final VariantScope scope) {
final BaseVariantData variantData = scope.getVariantData();
scope.setCompileTask(
taskFactory.create(
new TaskConfigAction<Task>() {
@NonNull
@Override
public String getName() {
return scope.getTaskName("compile", "Sources");
}
@NonNull
@Override
public Class<Task> getType() {
return Task.class;
}
@Override
public void execute(@NonNull Task task) {
variantData.compileTask = task;
variantData.compileTask.setGroup(BUILD_GROUP);
}
}));
scope.getAssembleTask().dependsOn(scope.getCompileTask());
}
為什麼我要專門說一下這個task,就是因為最後一句程式碼,AssembleTask依賴的該task,也就是說當我們執行AssembleTask的時候,該task會提前執行,而構建原理也在於此,該task也會依賴其他task,就這樣一層層依賴,構建時就會呼叫所有的相關task,這樣就完成了我們Android專案的構建。
四、結語
好了,終於梳理完成整個過程了,其實結合原始碼看文章,整個過程還是比較清晰的,不像Android原始碼那樣晦澀難懂,主要就是task的理解。
到這裡相信gradle再大家眼裡也不那麼神祕了,也有一定自己的理解了,接下來大家可以自行閱讀原始碼,梳理清晰構建的主要task都做了什麼,徹底掌握Android構建,這樣就可以為所欲為了哈哈哈哈,我就不再出相應文章了,是時候實戰一波了,下一篇文章將給大家帶來一篇比較實用的gradle外掛實現,順便也可以測試一下我們的學習成果了~
相關推薦
從Android Plugin原始碼開始徹底理解gradle構建:Task(三)
*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 系列文章: 一、前言回顧 首先我們依然回顧一下basePlugin裡的三個回撥: //plugin的基礎設定、初始化工作
android應用全域性資料的使用- 寫入資料: SharedPreferences(三)
寫入資料: SharedPreferences f = getSharedPreferences("conf", Context.MODE_PRIVATE); Shar
徹底理解Java的Future模式(轉)
技術分享 sse 數據結構 ride create GC .get AR 補充 先上一個場景:假如你突然想做飯,但是沒有廚具,也沒有食材。網上購買廚具比較方便,食材去超市買更放心。 實現分析:在快遞員送廚具的期間,我們肯定不會閑著,可以去超市買食材。所以,在主線程裏面另起
從零開始學 Web 之 JS 高級(三)apply與call,bind,閉包和沙箱
master 操作 console 概念 釋放 分享圖片 成功 num 命名沖突 大家好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公眾號:Web前端之巔
Android [Camera 原始碼] 相機 HAL3(Camera3) Google官方文件(二)
Google原始碼網地址連結:https://source.android.com/devices/camera 該Google Camera的文件為系列文章,文章列表: overview Camera3 HAL Subsystem Metadata and Con
從讀手冊開始讓zynq板卡跑起linux(三)------核心的編譯
經過一天的努力,終於編譯出核心,並且成功運行了,趕緊說說步驟: 1.從網上下載一個核心原始碼包“Linux-4.5.0-rc1-xilinx'; 2.拷貝“xilinx_zynq_defconfig”配置檔案到 "arch/arm/configs",執行 make ARC
Android官方技術文件翻譯——Gradle 外掛使用者指南(7)
本文譯自Android官方技術文件《Gradle Plugin User Guide》,原文地址:http://tools.android.com/tech-docs/new-build-system/user-guide。 翻譯不易,轉載請註明CSDN部落格上的出處:
Android官方技術文件翻譯——Gradle 外掛使用者指南(5)
昨晚把第五章未譯完的幾句話解決了,不過第六章沒怎麼譯,明後天又是週末,如果週一前第六章翻譯完的話,週一再發第六章。 本文譯自Android官方技術文件《Gradle Plugin User Guide》,原文地址:http://tools.android.com/te
Android官方技術文件翻譯——Gradle 外掛使用者指南(4)
最近趕專案,白天基本沒時間,只有晚上在家的時候才能看一看。昨天晚上只翻譯完了第四章,今天就只發第四章吧。 本文譯自Android官方技術文件《Gradle Plugin User Guide》,原文地址:http://tools.android.com/tech-doc
新手從零開始,相似影象匹配SIFT演算法(三),完結版
時隔半個月,終於可以提筆寫這篇從零開始學sift演算法的博文了! 經過再三折騰,突然回頭一看,發現SIFT並沒有想象的那麼難,也沒有想象的那麼強大(這裡不指那些改進的sift)!我自己是完全用java語言寫的,沒有用opencv,或者metlab等工具,雖然過程比較糾結,
深入理解Java內存模型(三)——順序一致性
內存空間 寫入 方便 語言 body 一半 同步 java語言 post 本文轉自:http://www.infoq.com/cn/articles/java-memory-model-3 數據競爭與順序一致性保證 當程序未正確同步時,就會存在數據競爭。java內存模型規範
我理解的數據結構(三)—— 隊列(Queue)
table can 需要 isempty sys 擴展 double start segment 我理解的數據結構(三)—— 隊列(Queue) 一、隊列 隊列是一種線性結構 相比數組,隊列對應的操作是數組的子集 只能從一端(隊尾)添加元素,只能從另一端(隊首)取出元素
深入理解Java多執行緒(三)
關於java多執行緒的概念以及基本用法:java多執行緒基礎 3, 執行緒間通訊 執行緒在作業系統中是獨立的個體,經過特殊的處理,執行緒間可以實現通訊,進而成為一個整體,提高CPU利用率 3.1,等待/通知機制 等待:wait()方法作用是使當前執
讀書筆記《深入理解Java虛擬機器》 (三)物件已死?與記憶體分配策略
物件是否可回收 引用計數演算法 給物件新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時就減1;當等於0時就認為物件不可能再被使用。問題:當兩個物件相互引用時,就無法回收了。 可達性分析演算法 通過一系列的稱為“GC Roots”的物件作為起
Android 學習之《Android程式設計權威指南》第二版 程式碼+筆記整理(三)
(程式碼)解決GeoQuiz應用旋轉恢復第一題的BUG 一、產生BUG的原因 1. 裝置旋轉時,系統會銷燬當前的QuizActivity例項,然後建立一個新的例項,這時陣列索引(mCurrentIndex)會初始化為0,因此使用者看到的還是第一道題目。 2.
mmap核心原始碼分析,基於核心版本3.10(三)
之前寫了(一)(二)其實就梳理到了get_unmapped_area的內容,而且有一點混亂,這裡進行第三篇的講解,講解在do_mmap_pgoff中除了get_unmapped_area的內容,來了解mmap的具體實現。通過(一)(二)(三)來將mmap核心原始碼進行一次梳理
不易理解易混淆的詞彙(三)
""" <axiner>宣告:(錯了另刂扌丁我) (如若有誤,請記得指出喲,謝謝了!!!) """ 併發: 同一時間,多個程式切換執行在同一cpu 並行: 同一時刻,多個程式分別執行在不同cup 同步非同步:訊息通訊機制 阻塞非阻塞:函式呼叫機制 ----->>
《深入理解計算機系統》筆記(三)連結知識【附圖】
歡迎檢視《深入理解計算機系統》系列部落格 --------------------------------------------------------------------------------------------------------------
Mybatis-generator修改原始碼實現自定義方法,返回List物件(三)
前兩篇文章我們講了如何獲取原始碼即建立工程、修改原始碼為dao(mapper)層新增一個方法,那麼這一篇,我們來講如何在xml新增這個方法所需要sql 3、實現XML檔案新增Dao(Mapper)層的實現 前面有講過,下圖中的兩個包,分別是管理dao(M
Chrome原始碼分析之程序和執行緒模型(三)
關於Chrome的執行緒模型,在他的開發文件中有專門的介紹,原文地址在這裡:http://dev.chromium.org/developers/design-documents/threading chrome的程序,chrome沒有采用一般應用程式的單程序多執行緒的模