深刻了解 Gradle

深刻了解 Gradle

看問題角度不一樣會有不同的理解,平時寫項目,在build.gradle 添加一些配置,寫多了配置,就會理所固然覺得Gradle就是一個配置文件,而在學習 Gradle 以前,須要明確一點,要深刻學習Gradle 說白了咱們須要把他當作是一個編程框架,而咱們須要瞭解的就是它的 API,並利用這些 API 完成一些任務。html

Gradle 工做流程生命週期

gradle工做流程

  • Initialization phase(初始化):這個階段執行 settings.gradle文件,解析本項目包含多少個 projectjava

  • Configration階段的目標是解析每一個project中的build.gradle。解析每一個子目錄中的 build.gradle,分別是加載插件,加載依賴,加載 Task 和執行腳本android

  • Execution phase(執行):這個階段就是執行任務git

  • 生命週期官方文檔描述github

Gradle 編程模型

  • Gradle是 groovy 語言編寫的,而 groovy 又基於Java,因此 Gradle 在執行 groovy 腳本的時候實際上是將其解析轉換成 Java 對象,而這種對象有三種基本類型編程

  • Gradle 對象:當咱們執行 gradle xxx 命令的時候,gradle 會從默認的配置腳本中構造出一個 Gradle 對象。在整個執行過程當中,只有這麼一個對象。Gradle 對象的數據類型就是 Gradle。api

  • Project 對象: 每個 build.gradle 會轉換成一個 Project 對象markdown

  • Settings 對象: 每個 settings.gradle 都會轉換成一個 Settings 對象閉包

每一個Gradle腳本都實現該Script接口。該接口定義了能夠在腳本中使用的許多屬性和方法app

官方文檔描述

Project

  • Project api 文檔

  • 由上一小節的Gradle編程模型中,每個 build.gradle 文件都會轉換成一個 Project 對象,Project和 build.gradle 文件之間存在一對一的關係。在 Gradle 術語中,Project 對象對 應的是 Build Script

  • 每個Project 項目包含不少個Task,Task就是對應插件,也能夠這樣說一個 Project中有多少個 Task 實際上是由插件的多少來決定的

,因此在build.gradle中,須要加載插件,加載依賴,設置屬性

加載插件

  • 一個 Project 對應 build.gradle,插件加載調用的是 Project 的 apply 函數,它其實定義在 Project 實現的 PluginAware接口中,以下爲官方文檔描述

apply官方文檔描述

  • 平時加載插件能夠是二進制 jar 包以下,也就是咱們很熟悉的加載插件,在 Project 目錄下 build.gradle
//若是 project 是編譯 Android APP,,則加載此插件
    apply plugin: 'com.android.application'
    //若是 project 是編譯 Library,則加載此插件
    apply plugin: 'com.android.library'

複製代碼
  • 同時 apply 函數也是能夠加載 gradle 文件的,好比建立統一依賴管理,能夠建立 config.gradle,裏面寫入依賴配置和版本號,而後在 build.gradle 中如使用

注意:build.gradle 中 apply 其餘 gradle 文件須要在同一個目錄下,不然須要文件路徑 + 文件名格式來 apply

apply from: 'config.gradle'
//文件路徑 + 文件名
apply from: rootProject.getRootDir().absolutePath+'/config.gradle'

複製代碼
  • config.gradle 文件

下文中有提到閉包,閉包,英文叫 Closure,是 Groovy 中很是重要的一個數據類型或者說一種概念;==它表明了一段可執行的代碼==

//除了 ext.xxx=value 這種定義方法外,還可使用 ext{}這種書寫方法
ext {
    //閉包
    android = [
            compileSdkVersion       : 28,
            buildToolsVersion       : "28.0.0",
            minSdkVersion           : 21,
            targetSdkVersion        : 28,
            versionCode             : 7,
            versionName             : "1.0.6",
            renderscriptTargetApi   : 21
    ]

    version = [
            supportLibraryVersion   : "28.0.0",
            smartrefreshVersion     : "1.1.0-alpha-25",
            okhttpVersion           : "3.12.0",
            retrofitVersion         : "2.3.0",
            glideVersion            : "4.8.0",
            daggerVersion           : "2.22.1",
            butterknifeVersion      : "8.8.1",
            fragmentationVersion    : "1.3.6",
    ]

    dependencies = [
            //base
            "appcompat-v7"                      : "com.android.support:appcompat-v7:${version["supportLibraryVersion"]}",
            "cardview-v7"                       : "com.android.support:cardview-v7:${version["supportLibraryVersion"]}",
            "support-v4"                        : "com.android.support:support-v4:${version["supportLibraryVersion"]}",
            "design"                            : "com.android.support:design:${version["supportLibraryVersion"]}",
            "recyclerview"                      : "com.android.support:recyclerview-v7:${version["supportLibraryVersion"]}",
            "constraint-layout"                 : "com.android.support.constraint:constraint-layout:1.1.3",
             .......
    ]
}

複製代碼
  • build.gradle 中使用 config.gradle 定義依賴也須要文件路徑和名稱結合
dependencies {
    implementation rootProject.ext.dependencies["appcompat-v7"]
}
複製代碼

Task

  • 首先照例貼出Task的Api文檔地址

  • Task 是 Gradle 中的一種數據類型,它表明了一些要執行或者要乾的工做。不一樣的插件

能夠添加不一樣的 Task。每個 Task 都須要和一個 Project 關聯

  • 能夠在 build.gradle 以下定義 task
// Task 是和 Project 關聯的,因此能夠利用 Project 的 task 函數來建立一個 Task
task myTask

task myTask { 
//閉包配置
configure closure 
}

//eg:
project.task("hello1"){
            doLast {
                println("Hello from the GreetingPlugin")
            }
}

Task myType << { task action } //注意,<<符號 是 doLast 的縮寫


//Task 建立的時候能夠指定 Type
task myTask(type: SomeType)

//eg:
task myTask(type:Copy) //建立的 Task 就是一個 Copy Task

task myTask(type: SomeType) { 
//閉包配置
configure closure 
}
複製代碼
  • 一個 Task 能夠有若干個 Action,每一個Task 都有doFirst 和 doLast 兩個函數,用於 添加須要最早執行的 Action 和須要和須要最後執行的 Action。Action 就是一個closure(閉包)。

  • 使用 task myTask { xxx}的時候,括號是一個 closure(閉包)。 gradle 在建立這個 Task 以後,返回給用戶以前,會先執行 closure 的邏輯任務

  • Task myType << { task action },能夠指定 Type,經過 type:名字表達。Gradle 本 身提供了一些通用的 Task,最多見的有 Copy 任務。Copy 是 Gradle 中的一個類。 當咱們:task myTask(type:Copy)的時候,建立的 Task 就是一個 Copy Task。

//文檔複製任務
task copyDocs(type: Copy) {
    from 'src/main/doc' //從src/main/doc目錄
    into 'build/target/doc' //複製到build/target/doc目錄
}
//更多例子能夠查看官方文檔
複製代碼
  • Copy文檔

  • 前面只是對 Task 一些概念進行了解,更多細節還需自行查看官方文檔。

Transform

不少第三方框架,都須要依賴自定義插件,好比阿里路由框架 ARouter,而它的實現中就有包含 Transform 的使用,瞭解並使用Transform 是讀懂第三方框架的基礎。

什麼是Transform

  • 首先能夠看看官方文檔,從1.5.0-beta1開始,Gradle插件包含一個Transform API,容許第三方插件在將已編譯的類文件轉換爲dex文件以前對其進行操做。

  • Transform 說白了也是一個Task,平時在 Android 編譯項目,項目代碼會先經過 compileJava 這個task 將項目源碼編譯成 .class文件,而 Transform 則能夠接收這些編譯產生的Class文件,而且 Transform 會在 compileJava 這個task 以後執行,這樣就表示能夠在 Android 項目生成 dex 以前作一些自定義操做。

  • Transform 依賴引入

implementation 'com.android.tools.build:gradle:4.0.0'
複製代碼

Transform API

  • Transform gradle 插件中的一個抽象類,有四個必需要實現的抽象方法,以下所示
@SuppressWarnings("MethodMayBeStatic")
public abstract class Transform {

    @NonNull
    public abstract String getName();

    @NonNull
    public abstract Set<ContentType> getInputTypes();
  
    @NonNull
    public abstract Set<? super Scope> getScopes();
    /**
     * Returns whether the Transform can perform incremental work.
     * 是否支持增量編譯
     * <p>If it does, then the TransformInput may contain a list of changed/removed/added files, unless
     * something else triggers a non incremental run.
     */
    public abstract boolean isIncremental();
    .....
}
複製代碼

Transform Task 名稱設置

  • 抽象方法 getName():實現該方法返回就是就是插件 Task 的名稱

Task 處理輸入文件類型

  • 抽象方法 getInputTypes():返回的是MutableSet<QualifiedContent.ContentType>類型集合,CLASSES類型表明只檢索 .class 文件,RESOURCES類型表明檢索 java 標準資源文件。
/**
     * The type of of the content.
     */
    enum DefaultContentType implements ContentType {
        /**
         * .class 文件
         */
        CLASSES(0x01),

        /**標準Java資源 */
        RESOURCES(0x02);

        private final int value;

        DefaultContentType(int value) {
            this.value = value;
        }

        @Override
        public int getValue() {
            return value;
        }
    }
複製代碼

Task 處理輸入文件範圍

  • 抽象方法 getScopes():返回的是 MutableSet 類型集合,Scope是個枚舉類型,取值含義以下
enum Scope implements ScopeType {
        /** 只檢索項目內容 */
        PROJECT(0x01),
        /** 只檢索子項目內容 */
        SUB_PROJECTS(0x04),
        /**只有外部庫 */
        EXTERNAL_LIBRARIES(0x10),
        /** 由當前變量測試的代碼,包括依賴項 */
        TESTED_CODE(0x20),
        /** 僅提供的本地或遠程依賴項 */
        PROVIDED_ONLY(0x40),
       ......
    }
複製代碼
  • 在TransformManager類中已經給咱們定義了一些枚舉的組合
public static final Set<ScopeType> PROJECT_ONLY = ImmutableSet.of(Scope.PROJECT);

public static final Set<ScopeType> SCOPE_FULL_PROJECT =
            ImmutableSet.of(Scope.PROJECT, Scope.SUB_PROJECTS, Scope.EXTERNAL_LIBRARIES);
            
public static final Set<ScopeType> SCOPE_FULL_WITH_FEATURES =
            new ImmutableSet.Builder<ScopeType>()
                    .addAll(SCOPE_FULL_PROJECT)
                    .add(InternalScope.FEATURES)
                    .build();
public static final Set<ScopeType> SCOPE_FEATURES = ImmutableSet.of(InternalScope.FEATURES);

public static final Set<ScopeType> SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS =
            ImmutableSet.of(Scope.PROJECT, InternalScope.LOCAL_DEPS);
            
public static final Set<ScopeType> SCOPE_FULL_PROJECT_WITH_LOCAL_JARS =
            new ImmutableSet.Builder<ScopeType>()
                    .addAll(SCOPE_FULL_PROJECT)
                    .add(InternalScope.LOCAL_DEPS)
                    .build();
複製代碼

Transform 方法

  • 核心處理方法 transform 它是一個空實現,核心的輸入內容 inputs 則是封裝到 TransformInvocation 對象中
override fun transform(transformInvocation: TransformInvocation) {
    
}
複製代碼
TransformInvocation
  • 它的成員參數以下
public interface TransformInvocation {


    /**
     * Returns the inputs/outputs of the transform.
     * @return the inputs/outputs of the transform.
     */
    @NonNull
    Collection<TransformInput> getInputs();
    
    /**
     * Returns the output provider allowing to create content.
     * @return he output provider allowing to create content.
     */
    @Nullable
    TransformOutputProvider getOutputProvider();

    .....
}
複製代碼
  • 輸入 inputs 對象爲 TransformInput,有輸入必然有輸出,輸出對象就是咱們把輸入文件作一些自定義處理好以後的文件,經過 TransformOutputProvider 獲取到輸出目錄,最後將修改的文件複製到輸出目錄
/**
 * The input to a Transform.
 * <p>
 * It is mostly composed of a list of {@link JarInput} and a list of {@link DirectoryInput}.
 */
public interface TransformInput {

    /**
     * Returns a collection of {@link JarInput}.
     */
    @NonNull
    Collection<JarInput> getJarInputs();

    /**
     * Returns a collection of {@link DirectoryInput}.
     */
    @NonNull
    Collection<DirectoryInput> getDirectoryInputs();
}
複製代碼
  • 經過接口 TransformInput 的定義能夠知道 transform 方法能夠出來到兩種輸入類型的文件,一直是 jar 包的集合jarInputs,另外一種是文件目錄集合 directoryInputs

  • 以下一個例子就是分別打印 輸入的 jar 包和 .class 文件名稱

override fun transform(transformInvocation: TransformInvocation) {
        println("transform 方法調用")
        //獲取 輸入 文件集合
        val transformInputs = transformInvocation.inputs
        transformInputs.forEach { transformInput ->
            // jar 文件處理
            transformInput.jarInputs.forEach { jarInput ->
                val file = jarInput.file
                println("find jar input: " + file.name)
            }
            //源碼文件處理
            //directoryInputs表明着以源碼方式參與項目編譯的全部目錄結構及其目錄下的源碼文件
            transformInput.directoryInputs.forEach { directoryInput ->
                //遍歷全部文件和文件夾 找到 class 結尾文件
                directoryInput.file.walkTopDown()
                    .filter { it.isFile }
                    .filter { it.extension == "class" }
                    .forEach { file ->
                        println("find class file:${file.name}")
                }
            }
        }
    }
複製代碼
  • 運行結果

transform遍歷

在插件中註冊 Transform

  • 根據前面學習,既然 Transform 是一個Task,要讓其生效,則要運行Gradle 的 Project中,Project 會加載插件,因此咱們須要在插件中註冊Transform task,以下所示,自定義 ASMLifecycleTransform ,在自定義插件中註冊它
/**
 * @Description: kotlin 代碼編寫自定義插件
 * @author maoqitian
 * @date 2020/11/13 0013 17:01
 */
class MainPlugin :Plugin<Project> {
    override fun apply(project: Project) {
        println("======自定義MainPlugin加載===========")
        //註冊執行自定義的 Transform task

        val asmTransform = project.extensions.getByType(AppExtension::class.java)
        println("=======registerTransform ASMLifecycleTransform ==========")
        val transform =  ASMLifecycleTransform()
        asmTransform.registerTransform(transform)
    }
}
複製代碼

更多文章

最後

  • 本文大多數內容都是來之官方文檔,要想深刻了解更多,能夠本身多多翻閱官方文檔資料,下一篇接着看自定義Gradle插件內容。
相關文章
相關標籤/搜索