美文网首页Android 技术开发Android开发经验谈Android开发
Gradle理论与实践四:自定义Gradle插件

Gradle理论与实践四:自定义Gradle插件

作者: 饱醉豚我去年买了个表 | 来源:发表于2018-12-28 14:21 被阅读50次

    Gradle系列相关文章
    1、Gradle理论与实践一:Gradle入门
    2、Gradle理论与实践二:Groovy介绍
    3、Gradle理论与实践三:Gradle构建脚本基础
    4、Gradle理论与实践四:自定义Gradle插件
    5、Gradle配置subprojects和allprojects的区别:subprojects和allprojects的区别

    Gradle插件

    Gradle可以认为是一个框架,负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译 Java 有 Java 插件,编译 Groovy 有 Groovy 插件,编译 Android APP 有 Android APP 插件,编译 Android Library 有 Android Library 插件。在Gradle中一般有两种类型的插件,脚本插件二进制插件。使用插件方式可以使得同一逻辑在项目中复用,也可以针对不同项目做个性化配置,只要插件代码支持即可。

    一、Java Gradle插件

    Java插件引入方式:

    • apply plugin: 'java'

    Java插件约定src/main/java为我们项目源代码存放位置;src/main/resources为资源存放位置;src/test/java为我们单元测试用例存放目录;src/test/resources存放我们单元测试中资源存放位置。
    java插件引入了一个概念叫做SourceSets,通过修改SourceSets中的属性,可以指定哪些源文件(或文件夹下的源文件)要被编译,哪些源文件要被排除。Gradle就是通过它实现Java项目的布局定义。

    默认配置:

    android {
        sourceSets {
            main {
                manifest.srcFile 'AndroidManifest.xml'
                java.srcDirs = ['src']
                resources.srcDirs = ['src']
                aidl.srcDirs = ['src']
                renderscript.srcDirs = ['src']
                res.srcDirs = ['res']
                assets.srcDirs = ['assets']
                jniLibs.srcDirs = ['libs']
            }
    
    }
    

    如果想修改源代码的目录以及多个resource的目录,可以通过下面来设置:

      sourceSets {
            main {
                java.srcDirs = ['other/java']
                res.srcDirs =
                        [
                                'src/main/res/',
                                'src/main/res/extra'
                        ]
            }
        }
    

    更多设置请移步官网:
    https://developer.android.com/studio/build/build-variants

    二、Android Gradle插件

    Android其实就是Gradle的一个第三方插件,Android Gradle和Android Studio完美无缝搭配的新一代构建系统。

    • APP插件id :com.android.application
    • Library插件id:com.android.library
    • Test插件id:com.android.test

    2.1、应用Android Gradle插件

    上面说了Android Gradle是Gradle的一个三方插件,托管在jcenter上,如果要使用,必须知道他们的插件id,另外还要配置他们依赖的classpath,在根目录的build.gradle中配置如下:

    buildscript {
        
        repositories {
            //代码仓库
            jcenter()
        }
        dependencies {
            //Android Gradle插件版本
            classpath 'com.android.tools.build:gradle:2.3.3'
        }
    }
    

    配置代码仓库为jcenter,当编译项目时,Gradle会去jcenter仓库中寻找Android Gradle对应版本的依赖,以上配置好后,就可以使用Android Gradle插件了,在我们app目录的build.gradle下:

    apply plugin: 'com.android.application
    
    android {
        compileSdkVersion 26 
        buildToolsVersion "26.0.3"
    
        defaultConfig {
            applicationId "org.ninetripods.qrcode"
            minSdkVersion 14
            targetSdkVersion 26
            versionCode 1
            versionName "1.0"
        }
    
        signingConfigs {
            release {
                keyAlias 'xxx'
                keyPassword 'xxx'
                storeFile file('xxx.jks')
                storePassword 'xxx'
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                signingConfig signingConfigs.release
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    

    android{}是Android Gradle插件提供的一个扩展类型,可以让我们自定义Android Gradle工程。

    • compileSdkVersion: compileSdkVersion告诉Gradle用哪个Android SDK版本编译应用
      1、应用想兼容新版本、使用了新版本API,此时就必须使用新版本及以上版本编译,否则就会编译报错;
      2、如果使用了新版本的Support Library,此时也必须使用新版本及以上版本编译

    • buildToolsVersion:构建该Android工程所用构建工具(aapt, dx, renderscript compiler, etc...)的版本,一般google在发布新的SDK时,会同时发布对应的buildToolsVersion,更详细的见:https://stackoverflow.com/questions/24521017/android-gradle-buildtoolsversion-vs-compilesdkversion
      PS:Android Studio 3.0以上时,buildToolsVersion不是必须要写的了。

    • defaultConfig: 默认配置,它是一个ProductFlavor。ProductFlavor允许我们在打多渠道包时根据不同的情况生成不同的APK包。即如果不在ProductFlavor中单独配置的话,那么会使用defaultConfig的默认配置。

    • minSdkVersion: 最低支持的Android系统API Level

    • targetSdkVersion: App基于哪个Android版本开发的

    • versionCode: App应用内部版本号,一般用来控制App升级

    • versionName: App应用的版本名称,即我们发布的App版本,一般用户可以看到。

    minSdkVersion、targetSdkVersion、compileSdkVersion三者的关系:
    minSdkVersion <= targetSdkVersion <= compileSdkVersion

    理想状态是:
    minSdkVersion(lowest possible) <= targetSdkVersion == compileSdkVersion(latest SDK)

    三、自定义Gradle插件

    编写自定义Gradle插件源代码的有下面三个地方:

    3.1、Build script

    可以在构建脚本中直接编写自定义插件的源代码。这样做的好处是插件可以自动编译并包含在构建脚本的classpath中,不需要再去声明。然而,这个自定义插件在构建脚本之外是不可见的,因此这种方式实现的插件在构建脚本之外是不能复用。
    举个例子,在根目录下的build.gradle中写入:

    //build.gradle
    class GreetingPlugin implements Plugin<Project> {
    
     @Override
     void apply(Project project) {
         //新建task hello
         project.task('hello') {
             doLast {
                 println 'Hello from the GreetingPlugin'
             }
         }
     }
    }
    //引入插件
    apply plugin: GreetingPlugin
    

    执行结果

    ./gradlew hello
    Hello from the GreetingPlugin
    

    3.2、buildSrc project

    在项目的根目录下新建一个buildSrc/src/main/groovy的目录,将自定义插件的源代码放入此目录中,Gradle将负责编译和测试插件,并使其在构建脚本的classpath中可见。这个插件对于项目中所有的build script都是可见的,但是在构建之外是不可见的,此构建方式不能再其他项目中复用。
    举例:在rootProjectDir/buildSrc/src/main/groovy目录下新建了一个插件类,如下:


    image.png

    GreetingExtensionPlugin.groovy中代码如下:

    //GreetingExtensionPlugin.groovy
    package com
    
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    class GreetingExtensionPlugin implements Plugin<Project> {
    
        @Override
        void apply(Project project) {
    
            // Add the 'greeting' extension object
            def extension = project.extensions.create('greeting', GreetingExtension)
    
            // Add a task that uses configuration from the extension object
            project.tasks.create('buildSrc') {
                doLast {
                    println "${extension.message} from ${extension.greeter}"
                    println project.greeting
                }
            }
        }
    }
    
    class GreetingExtension {
        String message
        String greeter
    }
    

    定义好的插件就可以在项目中所有的build.gradle中使用了:

    //build.gradle
    apply plugin: GreetingExtensionPlugin
    
    greeting {
        message = 'hello'
        greeter = 'GreetingExtensionPlugin'
    }
    

    执行结果:

    ./gradlew buildSrc
    hello from GreetingExtensionPlugin
    com.GreetingExtension_Decorated@42870556
    
    • 扩展属性:自定义插件代码中有一句def extension = project.extensions.create('greeting', GreetingExtension),可以用来扩展属性,可以理解为往GreetingExtension中新加了一个属性。创建完成后,可以通过project.greeting来获取扩展属性的实例。使用这种方式来给插件传递参数。

    3.3、Standalone project

    上面两种自定义插件都只能在自己的项目中使用,如果想在其他项目中也能复用,可以创建一个单独的项目并把这个项目发布成一个JAR,这样多个项目中就可以引入并共享这个JAR。通常这个JAR包含一些插件,或者将几个相关的task捆绑到一个库中,或者是插件和task的组合, Standalone project创建步骤:

    • 在Android Studio的rootProject目录下新建一个Module,类型随便选一个就行(如 Android Module),后面会有大的改动。(也可以选择IDEA来开发,IDEA中可以直接创建groovy组件)
    • 清空Module目录下build.gradle中的所有内容,删除其他所有文件
    • 在Module中创建src/main/groovy的目录,然后再创建包名文件夹。在main目录下再新建resources/META-INF/gradle-plugins目录,在这个目录下编写一个和插件id名字相同的.properties文件,这样Gradle就可以找到插件实现了。

    举个例子,下面的代码实现了在build目录中新建个文本文件,并写入文本的功能。
    1、在src/main/groovy下新建/com/fastgo/plugin目录并创建一个名为CustomPlugin.groovy的文件:

    package com.fastgo.plugin
    
    import org.gradle.api.DefaultTask
    import org.gradle.api.Project
    import org.gradle.api.Plugin
    import org.gradle.api.tasks.TaskAction
    
    class CustomPlugin implements Plugin<Project> {
        @Override
        void apply(Project project) {
            project.tasks.create('writeToFile', CustomPluginTask) {
                destination = { project.greetingFile }
                doLast {
                    println project.file(destination).text
                }
            }
        }
    }
    
    class CustomPluginTask extends DefaultTask {
        def destination
        File getDestination() {
            //创建路径为destination的file
            project.file(destination)
        }
        @TaskAction
        def greet() {
            def file = getDestination()
            file.parentFile.mkdirs()
            //向文件中写入文本
            file.write('hello world')
        }
    }
    

    上面的代码中在插件的apply(Project project)中创建了名为writeToFile的Task,并依赖于CustomPluginTaskCustomPluginTask中定义了一个destination路径,并通过project.file(destination)创建创建一个路径为destination的文件,并往文件中写入文本。

    注意:别忘了引入 package com.fastgo.plugin,否则最后生成后会提示找不到插件。

    2、创建resources/META-INF/gradle-plugins/com.fastgo.plugin.properties文件,并在文件里写入:

    implementation-class=com.fastgo.plugin.CustomPlugin
    

    3、在build.gradle中写入:

    plugins {
        id 'groovy'
        id 'maven-publish'
        id 'maven'
    }
    
    dependencies {
        implementation gradleApi()
        implementation localGroovy()
    }
    
    repositories {
        mavenCentral()
    }
    
    group = 'com.fastgo'
    version = '1.0-test'
    publishing {
        repositories {
            maven {
                url = uri("$rootDir/repo")
            }
        }
        publications {
            maven(MavenPublication) {
                from components.java
            }
        }
    }
    

    本例中是通过url = uri("$rootDir/repo")将代码打包到maven的本地仓库中,如果想上传到远端,换成远端链接即可。通过设置GroupId、ArtifactId、Version来保证插件的唯一性。

    • GroupId: group = 'com.fastgo'
    • ArtifactId: plugin
    • Version: version = '1.0-test'

    最终的文件如下:


    image.png

    插件编写完成后,在Android Studio的右上角打开Gradle,执行:plugin分组中的publish命令,执行完成后,会在项目根目录下生成repo仓库:

    image.png
    4、在项目根目录的build.gradle中引用插件:
    buildscript {
        repositories {
            maven {
                url = uri("$rootDir/repo")
            }
           ------其他-------
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.3'
            classpath 'com.fastgo:plugin:1.0-test'
        }
    }
     //通过插件id找到插件
     apply plugin: 'com.fastgo.plugin'
    
     ext.greetingFile="$buildDir/hello.txt"
    

    5、验证效果

    mqdeMacBook-Pro:AndroidStudy mq$ ./gradlew -q writeToFile
    hello world
    

    执行writeToFile的task输出了我们写入的文本,再看看build目录下是否有我们想要的文件:


    image.png

    可以看到在build目录下生成了hello.txt文件并往里面写入了设置的文本,说明我们编写的插件被成功引入了。

    四、源码地址

    上述例子源码已上传至:https://github.com/crazyqiang/AndroidStudy

    五、引用

    【1】https://docs.gradle.org/current/userguide/custom_plugins.html
    【2】https://docs.gradle.org/4.1/userguide/publishing_maven.html
    【3】https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

    相关文章

      网友评论

        本文标题:Gradle理论与实践四:自定义Gradle插件

        本文链接:https://www.haomeiwen.com/subject/plwslqtx.html