1. 程式人生 > >從Android Plugin原始碼開始徹底理解gradle構建:Task(三)

從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就是構建專案的基石,這裡我就放出任玉剛大佬總結的主要構建方法:

常用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沒有采用一般應用程式的單程序多執行緒的模