美文网首页
Android中的Gradle之玩转自定义功能

Android中的Gradle之玩转自定义功能

作者: aTaller | 来源:发表于2018-10-25 09:17 被阅读0次

    一、概述

    通过上一节Android中的Gradle之配置及构建优化,我们已经了解了Gradle的各个配置项的含义,知道了如何优化构建配置,但只会用别人提供好的,无法按自己的意愿实现功能。通过本章节,我们将简单介绍Groovy,了解Gradle中的Project与Task,引入gradle脚本,根据android plugin插件提供的功能自定义扩展,以及写自己的Task及自己的gradle插件,相信看完之后能对gradle有进一步的了解。

    二、Groovy语法简介

    Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming.

    上面的意思大致如下:
    Apache Groovy是一种功能强大,可选类型和动态语言,与静态类型和静态编译功能,支持Java平台,由于其简洁与易学性提高了开发人员的开发效率。它可以与任何Java程序平滑集成,并为您的应用程序提供强大的功能,包括脚本功能,域特定语言创作,运行时和编译时元编程以及函数编程。
    具体可以专题进行学习Groovy语法。

    1、与java比较

    • Groovy完全兼容java的语法,也就是说在Groovy可以编写java代码并运行,最终编译成java字节码。
    • 句末的分号是可选的
    • 类、方法默认是public的
    • 编译器自动添加getter/setter方法
    • 属性可以使用点号获取
    • 方法如果有返回值,最后一个表达式的值即作为返回,省略return。
    • ==等同于equals()
    • 没有NullPointerException

    2、Groovy高效特性

    • assert语句可以在任何位置断言
    • 弱类型变量
    • 调用方法如果有参数,可以省略括号
    • 字符串的表示
    • 集合类api,如map、list的某些方法
    • 闭包

    3、Groovy语法简单演示

    // 1 可选的类型定义
    def version = 1
    
    // 2 assert
    assert version == 2
    
    // 3 括号是可选的
    println version
    
    // 4 字符串
    def s1 = 'Groovy'
    def s2 = "version is ${version}"
    def s3 = '''三个
    分号
    可以
    换行
    '''
    println s1 // Groovy
    println s2 // version is 1
    println s3   // 三个
                    // 分号
                    // 可以
                    // 换行
    
    // 5 集合api
    // list
    def buildTools = ['ant','maven']
    buildTools << 'gradle'
    println buildTools.getClass() // class java.util.ArrayList
    assert buildTools.size() == 3 // 没有异常
    // map
    def buildYears = ['ant':2000,'maven':2004]
    buildYears.gradle = 2009
    println buildYears.ant  // 2000
    println buildYears['gradle'] // 2009
    println buildYears.getClass() // class java.util.LinkedHashMap
    
    // 6 闭包
    def c1 = {
        v ->
                println v
    }
    def method1(Closure closure){
        closure('param')
    }
    method1(c1) // param
    
     method1{ 
        c1 "hello"
    }
    
    
    
    

    三、Gradle中的基础概念

    1、Project

    可以说每一个build.gradle都是一个Project对应项目中就是某一个module,直接在其中定义的属性或者方法都可以使用project来调用。如def valueTest = 5可以使用valueTest 或者project.valueTest来获取valueTest的值。

    根目录的build.gradle也是一个Project,在其中定义ext{valueTest = 5},在module中的build.gradle中可以直接使用rootProject.valueTest或者rootProjext.ext.valueTest来引用。这里的ext是一个全局变量的意思。

    2、Task

    Project又是由一个或者多个Task组成,Task存在着依赖关系,依赖关系又保证了任务的执行顺序。比如我们熟知的Task有clean,jar,assemble等。

    3、Gradle构建声明周期

    Gradle的声明周期分为三段:

    • 初始化阶段:读取根工程中 settings.gradle 中的 include 信息,决定有哪几个工程加入构建,创建Project实例,如 include ':app',':example'
    • 配置阶段:执行所有工程的 build.gradle 脚本,配置Project对象,创建、配置Task及相关信息。该阶段会执行build.gradle中的命令,如直接写在Project中的println命令
    • 运行阶段:根据gradle命令传递过来的task名称,执行相关依赖任务

    四、Gradle脚本的引入

    引入gradle脚本,可以实现对gradle代码的复用,减少维护成本。可以通过apply from: 'other.gradle'来对other.gradle进行引入。日常开发中,我们可以通过如下几点优化我们的项目:

    1、将项目中的配置复用项提取出来

    在gradle中有很多配置项,如版本号,版本名称,最小支持SDK版本,是否使用混淆,依赖的第三方库等。这些配置很有可能在项目中复用,如需修改,可能会导致遗漏,将这些配置提取出来,供各个build.gradle使用。具体如下:

    1、在根目录新建config.gradle

    ext {
    
        android = [
                compileSdkVersion: 27,
                minSdkVersion    : 16,
                targetSdkVersion : 27,
                versionCode      : 1,
                versionName      : "1.0.0",
                multiDexEnabled  : true
        ]
    
        version = [
                kotlin_version       : "1.2.50",
                support_version      : "27.1.1",
                constraint_version   : "1.1.3",
                junit_version        : "4.12",
                runner_version       : "1.0.2",
                espresso_core_version: "3.0.2"
        ]
    
        dependencies = [
                "androidJUnitRunner": "android.support.test.runner.AndroidJUnitRunner",
                "kotlin"            : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${version["kotlin_version"]}",
                "appcompat_v7"      : "com.android.support:appcompat-v7:${version["support_version"]}",
                "constraint_layout" : "com.android.support.constraint:constraint-layout:${version["constraint_version"]}",
                "junit"             : "junit:junit:${version["junit_version"]}",
                "runner"            : "com.android.support.test:runner:${version["runner_version"]}",
                "espresso_core"     : "com.android.support.test.espresso:espresso-core:${version["espresso_core_version"]}"
        ]
        
    }
    

    2、在根目录build.gradle中增加apply from:"config.gradle"

    3、修改module的gradle文件

    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        defaultConfig {
            applicationId "net.loosash.learngradle"
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
            testInstrumentationRunner rootProject.ext.dependencies["androidJUnitRunner"]
        }
        ...
    }
    ...
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation rootProject.ext.dependencies["kotlin"]
        implementation rootProject.ext.dependencies["appcompat_v7"]
        implementation rootProject.ext.dependencies["constraint_layout"]
        testImplementation rootProject.ext.dependencies["junit"]
        androidTestImplementation rootProject.ext.dependencies["runner"]
        androidTestImplementation rootProject.ext.dependencies["espresso_core"]
    }
    

    2、对于build.gradle中重复部分进一步提取

    1、对于依赖工程或者组件化工程,建议将依赖module中的配置也提取出来。在根目录新建default.gradle文件。

    apply plugin: 'com.android.library'
    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        
        defaultConfig {
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
            testInstrumentationRunner rootProject.ext.dependencies["androidJUnitRunner"]
        }
        
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        // support
        implementation rootProject.ext.dependencies["appcompat_v7"]
        // test
        testImplementation rootProject.ext.dependencies["junit"]
        androidTestImplementation rootProject.ext.dependencies["runner"]
        androidTestImplementation rootProject.ext.dependencies["espresso_core"]
    }
    
    

    2、修改使用默认配置的module中的build.gradle文件

    apply from:"../default.gradle"
    
    android{
        // 写入该模块特定的配置
        resourcePrefix "example_" //给 Module 内的资源名增加前缀, 避免资源名冲突
    
    }
    
    dependencies {
        // 该模块使用的依赖
    
    }
    
    

    3、对build.gradle文件前后做一个对比

    修改后的好处在于,比如在工程中多处使用support包中的依赖,一次修改版本号即可对全部工程生效,降低了维护成本。


    修改前
    修改后
    修改前
    修改后
    修改前
    修改后

    五、自定义Task任务

    1、定义任务

    最常用的写法,执行./gradlew hello打印出hello world

    task hello{
        println 'hello world'
    }
    

    在android studio创建项目后,会在根目录的build.gradle中创建clean任务,就是删除build文件夹下文件

    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    说明:Task创建的时候可以通过 type: SomeType 指定Type,Type其实就是告诉Gradle,这个新建的Task对象会从哪个基类Task派生。比如,Gradle本身提供了一些通用的Task,最常见的有Copy 任务。Copy是Gradle中的一个类。当我们:task myTask(type:Copy)的时候,创建的Task就是一个Copy Task。类似的,有如下写法:

    task copyDocs(type: Copy) {
        from 'src/main/doc'
        into 'build/target/doc'
    }
    

    2、依赖关系

    Gradle中Task存在着依赖关系,在执行过程中会先执行所依赖的任务,再执行目标任务。

    task hello {
        doLast {
            println 'Hello world!'
        }
    }
    Task intro(dependsOn: hello) {
        doLast {
            println "I'm Gradle"
        }
    }
    

    执行结果

    > Task :hello
    Hello world!
    
    > Task :intro
    I'm Gradle
    
    

    3、分组和描述

    task可以增加分组和说明

    task hello {
        group 'Custom Group 1'
        description 'This is the Hello Task'
        doLast {
            println 'Hello world!'
        }
    }
    task intro(dependsOn: hello) {
        group 'Custom Group 2'
        description 'This is the intro Task'
        doLast {
            println "I'm Gradle"
        }
    }
    

    输入./gradlew tasks

    Custom Group 1 tasks
    --------------------
    hello - This is the Hello Task
    
    Custom Group 2 tasks
    --------------------
    intro - This is the intro Task
    

    4.获取已经定义的任务,并增加执行处理

    在上面已有的Task hello中增加执行处理。

    task hello {
        group 'Custom Group 1'
        description 'This is the Hello Task'
        doLast {
            println 'Hello world!'
        }
    }
    task intro(dependsOn: hello) {
        group 'Custom Group 2'
        description 'This is the intro Task'
        doLast {
            println "I'm Gradle"
        }
    }
    tasks.hello{
        doFirst{
            println "prepare to say"
        }
    }
    
    

    执行./gradlew intro得到如下输出

    > Task :hello
    prepare to say
    Hello world!
    
    > Task :intro
    I'm Gradle
    
    

    六、自定义构建功能

    1、buildTypes

    可以利用这里的属性,针对debug版本和release版本对包名进行修改。具体使用如下:

    1)对applicationId进行修改,使其能同时安装debug版本和release版本

    android {
        ...
        buildTypes {
            debug {
                // bebug版本包名为xxx.xxx.xxx.debug
                applicationIdSuffix ".debug"
            }
            ...
        }
    }
    

    2)对release版本进行签名配置

      signingConfigs {
            release {
                keyAlias 'xxxx'
                keyPassword 'xxxxxx'
                storeFile file('your-keystore-path')
                storePassword 'xxxxxx'
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                signingConfig signingConfigs.release
    
            }
        }
    

    2、productFlavors

    可以根据该属性进行生成不同的APK版本,版本可以携带不同的特性,最常用的就是多渠道打包、根据不同环境生成不同APK。

        productFlavors{
            productA{
                // 定义特定版本号
                // applicationIdSuffix ".a"
                applicationId "xxx.xxx.xxx.a"
                // 定义特定版本名称
                versionName "version-a-1.0"
                // 定义特定的BuildConfig
                buildConfigField("String","CUSTUMER_CONFIG","xaxaxaxa")
            }
            productB{
                applicationId "xxx.xxx.xxx.b"
                versionName "version-b-1.0"
                buildConfigField("String","CUSTUMER_CONFIG","xbxbxbxb")
            }
        }
        
    dependencies{
        ...
        // 特定版本依赖
        productACompile 'io.reactivex.rxjava2:rxjava:2.0.1'
        ...
    }
    
    

    3、增加自定义处理

    将生成的apk以自定义命名复制到自定义文件夹

        applicationVariants.all { variant ->
            tasks.all {
                if ("assemble${variant.name.capitalize()}".equalsIgnoreCase(it.name)) {
                    it.doLast {
                        copy {
                            rename { String fileName ->
                                println "------------${fileName}--------------"
                                fileName.replace(".apk", "-${defaultConfig.versionCode}.apk")
                            }
                            def destPath = file("/Users/solie_h/Desktop/abc/")
                            from variant.outputs.first().outputFile
                            into destPath
                        }
                    }
                }
            }
        }
    

    七、总结

    到这里,其实其实刚刚是使用Gradle的开始,我们可以操作Gradle完成一些自己的需求,但想对外提供Gradle插件还需要一些功夫,接下来还要继续对android plugin源码进行研读,也找一些有Gradle插件的开源项目进行学习,如Replugin、Tinker,进一步提高自己对Gradle的认识。

    关注微信公众号,最新技术干货实时推送

    相关文章

      网友评论

          本文标题:Android中的Gradle之玩转自定义功能

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