Android中Gradle实用指南

了解Gradle 得心应手

Gradle让Android中的依赖管理、库管理、渠道管理以及一些动态地编译配置变得极为方便!!
本文是对Gradle在Android项目中进阶使用的知识点整理和简要讲解
较为详细的Gradle教科书Github
Gradle脚本配置文档:Google Github


Android工程中的Gradle

下面简述对我们工程最重要的几个Gradle文件,后续也会围绕他们进行详细讲解和补充
(请仔细看代码中的注释哈)
- 工程Project 中的 build.gradle : 工程控制Gradle编译配置
- 模块module中的 build.gradle : 控制每个Module的编译过程
- gradle.properties : gradle动态参数的配置文件
- local.properties : 本地的配置,如:SDK位置
- gradle-wrapper.properties :gradle本地代理,声明了指向目录和版本
- distributionUrl : 指定gradle版本不存在时,就从Value的地址中去下载。很多时候,我们只要版本换成我们本地存在的gradle版本就可以了
- settings.gradle : 配置Gradle中的Module管理

常用Gradle Task

~ 表示 gradlew
gradlew是包装器,自动下载包装里定义好的gradle 版本,保证编译环境统一
gradle 是用本地的gradle
- gradlew task -all : 罗列出所有Task ,同时携带具体作用和相互关系
- gradlew assembleDebug : 导出所有渠道测试包
- ~ assembleRelease : 导出所有渠道正式包
- ~ assembleBaiduDebug --stacktrace : 导出指定渠道测试包,同时携带异常信息

  • ~ --stop : 立即停止编译
  • ~ check : 检查任务
  • ~ build : 执行了 check和assemble
  • ~ clean : 清除所有中间编译结果

动态参数配置

在Gradle中动态配置资源参数
我们可以根据各自的需求在不同的领域(如:buildType 的debug, defaultConfig ...)下去动态替换或配置我们项目中所使用到的资源,如 log 开关, 针对不同渠道的对应内容字段,不同版本定义引入的不同值等等

首先说清一点,对于动态资源在build.gradle中多个领域中的使用,会遵循一下顺序来进行覆盖:
buildType > productFlavor > defaultConfig > Manifest中的配置 > 依赖的第三方库的配置 > 任意领域中的默认值(也就是没有设置值)

  1. Manifest占位符: 可以动态配置Manifest的参数
        在Manifest的Application节点下
        //这里以友盟为例
        <!-- 友盟统计相关meta-data -->
        <meta-data
            android:name="UMENG_APPKEY"
            android:value="balabalabala" />
        <meta-data
            android:name="UMENG_CHANNEL"
            android:value="${UMENG_CHANNEL_VALUE}" />

在build.gradle中对参数进行动态配置
 productFlavors {
        baidu {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
        }
}
  1. gradle.properties 的使用
    • 系统属性:
      配置: systemProp.proName=123
      使用:System.properties['proName']
    • 自定义属性 :
      配置: 有key: ray.proName=123 ,无key : proName = 123
      使用: 有key: project。property('proName') , 无key : proName
gradle.properties中的配置

#使用系统参数配置
systemProp.keyStore=ray.jks
#使用key/value键值对配置
ray.keyPassword=123456
#属性配置
mKeyAlias=ray

build.gradle中的使用
//签名打包
        release {
            //签名文件所在路径
            storeFile file(System.properties['keyStore'])
            //签名密码
            storePassword "111111"
            //别名
            keyAlias mKeyAlias
            keyPassword project.property('ray.keyPassword')
        }


  1. BuildConfig文件
    • app/build/generated/source/buildConfig文件夹下面
    • 使用: buildConfigField "String" , "key" , "\"value\""
      配置buildConfig中的属性参数 String : 参数类型(int,boolean...), key : 属性的名字, value : 属性的值, \ 为转义字符
  2. resValue 动态修改工程资源
    • 和buildConfig 类似, resValue("string","key","value")
    • 其中string 表示 会在 app/build/generated/res/resValue/.../generated.xml中生成对应的String 的 key 和Value, 代码中可以直接getResources().getString(R.string.key);获取到value
    • 注意: 因为Gradle编译的时候会将脚本配置和string.xml文件中配置进行merge,所以string.xml中已经存在的key在编译前要删除,否则会报错
//下面模拟在不同渠道下修改资源参数
productFlavors{
baidu{
  buildConfigField "String" , "productCode" , "\"baidu 1.0\""
  resValue("string","productName","baidu")
}
}

Project : build.gradle

全局属性配置

  1. 在project : build.gradlebuildscript中声明ext和自定义属性,然后在其他module中就可以直接使用这个属性了
    如下:

Project : build.gradle buildscript { //自定义工程使用的属性 ext { kotlin_version = '1.1.0' compile_version = 25 } //声明依赖Android Gradle 插件版本 dependencies { classpath 'com.android.tools.build:gradle:2.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } Module : build.gradle 中使用 Android { compileSdkVersion compile_version } dependencies { compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" }
  1. 新建自己的Gradle属性文件 text.gradle , 然后在Project中引入,最后就可以在其他的module中直接使用了,具体应用可以参考印象笔记Github
    如下:
text.gradle 编写如下:
ext {
    kotlinVersion = "1.1.0"
    rxjavaLibVersion = "1.2.0"

    dependencies = [
            // Rx
            rxJava: "io.reactivex:rxjava:$rxjavaLibVersion"
    ]
}

Project : build.gradle 中引入
//就是引入他的相对根目录路径
apply from: 'config/text.gradle'

Module : build.gradle 中使用
rootProject.ext.XXXXX

dependencies {

 def dependencies=rootProject.ext.dependencies

    compile dependencies.rxJava

    compile "org.jetbrains.kotlin:kotlin-stdlib:$rootProject.ext.kotlinVersion"

}

整体结构与描述

//声明引入的参数配置文件
apply from: 'config/dependencies.gradle'
apply from: 'config/text.gradle'

//编译配置
buildscript {

    //自定义参数
    ext {
        kotlin_version = '1.1.0'
        compile_version = 25
    }

    //Gradle指定使用jcenter代码仓库
    repositories {
        jcenter()
    }
    //声明依赖Android Gradle 插件版本
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

//这里可以为项目整体配置属性
 allprojects{
    repositories {
        jcenter()
    }
}

//任务:每次构建的时候删除指定目录
task clean(type: Delete) {
    delete rootProject.buildDir
}

Module : build.gradle

控制每个Module的编译过程以及具体的参数配置

defaultConfig

基本配置信息

  //默认配置
    defaultConfig {
        //包名
        applicationId "com.rayhahah.gradledemo"
        //最低版本
        minSdkVersion 19
        //目标版本
        targetSdkVersion 25
        //版本代码
        versionCode getVersinCode()
        //版本
        versionName "1.0"
        //自动化测试
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        resValue "int","test","1"
    }

signConfigs

签名配置信息

signingConfigs {

        //debug模式签名文件
        debug {}
        //签名打包
        release {
            //签名文件所在路径
            storeFile file("ray.jks")
            //签名密码
            storePassword "111111"
            //别名
            keyAlias "rayhahah"
            keyPassword "111111"
        }

        //自定义签名配置
        ray{
            //和上面的属性一致,根据个人需求实现不同配置
        }
    }

buildTypes

编译类型 : 指定编译不同类型情况下的不同配置信息
这里是列举了部分属性和方法,全部的方法和属性请看官网文档

//构建配置
    buildTypes {
        release {

            //是否启用资源优化
            minifyEnabled
             //启用舍弃无用资源,只有当开启混淆才能够启用
            shrinkResources false
            //指定混淆文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

            //指定我们release包的输出文件名就是我们的渠道名字
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith(".apk")) {

                        def fileName = "${variant.productFlavors[0].name}" + ".apk"
                        output.outputFile = new File(outputFile.parent, fileName);
                    }
                }
            }
        }

        debug {

        }

        //继承
//        rayhahah.initWith(debug)

        //自定义buildType
        rayhahah {
            //指定签名配置文件
            signingConfig signingConfigs.debug
            //包名增加后缀
            applicationIdSuffix ".ray"

        }
    }

sourceSets

配置资源逻辑组

  • 通过指定资源 文件夹路径构建自己的工程目录
指定Android所需要文件夹所在具体路径

sourceSets {
//这样的配置适用于将Eclipse中的项目结构迁移到AndroidStudio中
        main {
            //指定src资源目标目录
            java.srcDirs = ['src']
            //指定asset的目标目录
            assets.srcDirs = ['assets']
            //指定res的目标目录
            res.srcDirs = ['res']
            //指定依赖C文件的目标目录
            jni.srcDirs = ['jni']
             //指定依赖so文件的目标目录
            jniLibs.srcDirs = ['libs']
            //指定Manifest的目标文件路径
            manifest.srcFile 'AndroidManifest.xml'
        }
 }
  • 常用! 给自己的layout分包管理!!
1. 在res下新建文件夹 layouts(其实叫什么都无所谓)
2. 然后在 layouts下 新建你要分的包 如: activity,fragment 或按照业务模块来分
3. 在分包内新建Android resource directory -> layout 不要改名字
4. 在module:build gradle 如下配置
5. 然后将以前的layout文件拷贝到对应分包的layout下就可以使用了

PS:只有在Project目录才能看到,Android目录结构是看不到的

 sourceSets {
        main {
            res.srcDirs = [
                    'src/main/res/layouts/activity',
                    'src/main/res/layouts/fragment',
                    'src/main/res/layouts',
                    'src/main/res'

            ]
        }
    }

productFlavors

打包渠道配置信息(仔细看代码注释)

//多渠道打包配置
//利用Manifest占位符动态参数配置
productFlavors {
        baidu {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
        }

        wandoujia {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
        }

        googleplayer {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "googleplayer"]
        }

        //不想每一个都去配置渠道名称也可以用下面这个函数
        //这个函数可以将 Manifest中的占位符替换成 每个渠道的名字
        productFlavors.all {
            flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
        }
    }

compileOptions

 //编译配置项
    //主要配置Java编译版本
    compileOptions {
        sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8
        targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8
    }

lintOptions


//lint配置项 lintOptions { //启用出错停止grgradle构建 abortOnError false // true--检查所有问题点,包含其他默认关闭项 checkAllWarnings true // 关闭指定问题检查 disable 'TypographyFractions','TypographyQuotes' // 打开指定问题检查 enable 'RtlHardcoded','RtlCompat', 'RtlEnabled' // 仅检查指定问题 check 'NewApi', 'InlinedApi' // true--生成HTML报告(带问题解释,源码位置,等) htmlReport true // html报告可选路径(构建器默认是lint-results.html ) htmlOutput file("lint-report.html") // 忽略指定问题的规则(同关闭检查) ignore 'TypographyQuotes' }

build.gradle 图形化配置

AndroidStuido 给我们提供了十分友好地对于Build.gradle 图形化配置的界面,使用如下:
- File -> Project Structure -> 选择需要配置的 Module —> 选择需要配置的领域
如图十分直观:

图形化界面

dependencies 项目依赖

  1. build.gradle中dependencies中的配置解析
//dependencies : 当前Android Module构建过程中所依赖的所有库
dependencies {
    //依赖指定目录下所有指定后缀的文件
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //测试工具依赖
    testCompile 'junit:junit:4.12'
    //远程库的依赖 (当从远程库中下载一次过后,就会缓存到本地了)
    //默认远程库配置为 jcenter() 
    compile 'com.android.support:appcompat-v7:25.2.0'

    //依赖指定文件(这里依赖的是jar包)
    compile file('libs/test-1.0.0.jar')
    //依赖本地项目库
    compile project(':testLibrary')

    //格式:  groupId: com.squareup.retrofit2
    //       artifactId : retrofit
    //       version:  2.1.0
    // SNAPSHOT : 表示依赖 retrofit 及其依赖的所有项目,如果他所依赖的项目在本项目中重复出现依赖,则只依赖retrofit项目中的。
    // @aar :  表示只依赖retrofit,不依赖他所依赖的项目
    compile ('com.squareup.retrofit2:retrofit:2.1.0-SNAPSHOT@aar')
    {
         //强制刷新远程库,避免远程库刷新,本地未更新
         transitive = true        
          //exclude : 单独去除okhttp3的依赖
         exclude module : 'com.squareup.okhttp3:okhttp:3.3.0' 
    }

}
  1. so库依赖
    • src/main目录下创建jniLibs,然后将so文件拷贝进去就可以了
    • 当然也可以通过sourceSet指定jniLib的目标目录来自定义管理依赖的so文件存放
  2. 本地Module依赖
    • build.gradledependencies 领域中添加 compile project(':testLibrary')
    • setting.gradle 中添加module到include中 如:include ':app',':testLibrary'

方法def定义

在Gradle中你可以写方法供 配置信息动态调用

//自定义函数
def getVersinCode() {
//    ......
}

Android{
    defaultConfig{
        versionCode getVersinCode()
    }
}

Gradle编译提速优化

检测

  • gradlew build -profile : 编译工程同时生成编译性能分析文件,在根目录build/reports/profile/profile-xxxx.xxx....html ,通过浏览器打开以后
    如图: 我们需要优化的就是Task Execution

profile.html

  • 往下拉,可以看到TaskExcution的详细参数

TaskExcution

可以看到lint耗时最多,然后我们就可以根据自己项目中的具体情况来做优化

禁用Task达到提速

  • 根据上面的耗时来对应禁用Task来达到提速的效果
  • Project:build.gradle 中的 buildScript中动态配置编译时禁用即可, 代码:gradle.startParameter.excludedTaskNames.add('lint') 就可以实现禁用了,具体需要继续禁用的可以根据项目输出的编译分析文件来作出添加和调整

AAPT

aapt即Android Asset Packaging Tool,在SDK的build-tools目录下。该工具可以查看,创建, 更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件,尽管你可能没有直接使用过aapt工具,但是build scripts和IDE插件会使用这个工具打包apk文件构成一个Android 应用程序(百度百科)

  • 在Debug模式下,我们需要优化AAPT来大量提速我们的编译(记得在release下改回来)
aaptOtions{
    cruncherEnabled = false
}

Gradle编译优化

  • 提升Gradle本身编译速度
    • gradle.properties中配置
//开启守护线程支持
org.gradle.daemon=true

//开启并行编译
org.gradle.parallel=true
//按需编译
org.gradle.configureondemand=true

//手动配置Gradle编译时内存分配
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# 开启JNI编译支持过时API
android.useDeprecatedNdk=true
  • build.gradle中配置
//增加编译内存到4g
dexOptions{
    incremental true    
    javaMaxHeapSize "4g"
}

认识Task

  • Task基本使用:

task testTask << { println 'testTask << print' // 表示在task最前面来执行的过程 doFirst { println 'testTask do first'} // << 和 doLast表示对当前task追加执行过程,效果是一样的 doLast{ println 'testTask do last!'} } //task之间的依赖 dependsOn //当执行存在依赖的task时,会先执行他的父类也就是依赖目标 task testDependsOn(dependsOn:testTask){ println 'testDependsOn default print ' } //或者 testDependsOn.dependsOn testTask //当执行testDependsOn是 打印顺序: testDependsOn default print -> testTask do first -> testTask << print -> testTask do last! //顺序总结为: //1.不加doLast和doFirst的最先执行 //2.依赖task优先级高于自己的doFirst和doLast //3.同一个task中的doLast按从上向下顺序执行 //4.同一个task中的doFirst按从下到上倒序执行 //5.同一个task的doFirst优先级高于doLast //显示声明类型为Copy, 不声明默认为defaultTask task testCopy(type : Copy){ //将当前gradle文件从src目录拷贝到dst目录 from "src" into "dst" } //每一个特定的Task类型还可以含有特定的Property,比如Copy的from和to等。 //自定义property ext.testProperty = "" task testExtProperty << { //直接使用自定义的property println testProperty }
  • 自定义Task:
//局部自定义Task
//直接在build.gradle中自定义Task
//但是也只能在当前module中引用
class TestCustomTask extends DefaultTask {
    //@Optional,表示在配置该Task时,message是可选的。
    @Optional
    String message = 'I am jjx'
    //@TaskAction表示该Task要执行的动作,即在调用该Task时,hello()方法将被执行
    @TaskAction
    def hello() {
        println "hello world $message"
    }
}

//hello使用了默认的message值
task hello(type: TestCustomTask)

//重新设置了message的值
task helloOne(type: TestCustomTask) {
    message = "I am a android developer"
}


全局自定义Task
如果需要自定义大量的Task,就要新建一个Gradle文件来统一管理
通过apply来引入使用
//这是插件
apply plugin: 'com.android.application'
//这里gradle-quality.gradle就是另外单独定义了task的gradle
apply from: '../build-config/gradle-quality.gradle'

自定义Plugin

自定义Plugin可以让我们在工程编译根据需求中自动去完成一些操作
下面就是一个编译后自动打印当前时间的Plugin

build.gradle中直接定义

与自定义Task十分类似
可以在build.gradle中自定义plugin
apply plugin: DateAndTimePlugin

dateAndTime {
    timeFormat = 'HH:mm:ss.SSS'
    dateFormat = 'MM/dd/yyyy'
}

class DateAndTimePlugin implements Plugin<Project> {
    //该接口定义了一个apply()方法,在该方法中,我们可以操作Project,
    //比如向其中加入Task,定义额外的Property等。
    void apply(Project project) {
        //加载Extension
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)

        //使用Extension配置信息
        project.task('showTime') << {
            println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
        }

        project.tasks.create('showDate') << {
            println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
        }
    }
}
//每个Gradle的Project都维护了一个ExtenionContainer,
//我们可以通过project.extentions进行访问
//比如读取额外的Property和定义额外的Property等。
//向Project中定义了一个名为dateAndTime的extension
//并向其中加入了2个Property,分别为timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}

工程中定义

其实本质上就是对上面的自定义Plugin结构化拆解

  1. Plugin目录创建
    • 在根目录下创建buildSrc
    • 子目录结构如下:buildSrc/src/main/groovy/com/raybuildSrc/src/main/resources/META-INF/gradle-plugins
  2. 创建buildSrc/build.gradle , 配置如下
apply plugin:'groovy'

dependecies{
    compile gradleApi()
    compile localGroovy()
}
  1. Plugin 逻辑实现
    自定义Plugin的主要实现逻辑
    buildSrc/src/main/groovy/com/ray 下创建DateAndTimePlugin
class DateAndTimePlugin implements Plugin<Project> {
    void apply(Project project) {
        //加载Extension
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)

        //使用Extension中的配置信息
        project.task('showTime') << {
            println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
        }

        project.tasks.create('showDate') << {
            println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
        }
    }
}
  1. Extension实现
    Extension相当于Gradle配置信息(相当于实体类),然后主项目的build.gradle 通过Extension传递配置(相当于赋值)

同样在buildSrc/src/main/groovy/com/ray下创建DateAndTimePluginExtension

//每个Gradle的Project都维护了一个ExtenionContainer,
//我们可以通过project.extentions进行访问
//比如读取额外的Property和定义额外的Property等。
//向Project中定义了一个名为dateAndTime的extension
//并向其中加入了2个Property,分别为timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}
  1. Plugin命名
    自定义外部引用时Plugin的名字
    buildSrc/src/main/resources/META-INF/gradle-plugins 下创建
    timePlugin.properties ,内容只有一行代码 : implementation-class = com.ray.DateAndTimePlugin
  2. Plugin的使用
    主项目中apply plugin:'timePlugin'
    可选:配置Extension :

timePlugin{
    //动态修改和配置Extension属性
    //这里修改了日期格式
    timeFormat = 'MM/dd/yyyy'
}

独立Module自定义插件

  1. 新建plugin目录结构如图:

ModulePlugin目录

  1. buildSrc/build.gradle的修改 如下:
apply plugin: 'groovy'
//增加Maven的支持
apply plugin: 'maven'

version = 1.0
group = 'com.ray.plugin'
archivesBaseName = 'timeplugin'
repositories.mavenCentral()

dependencies {
    compile gradleApi()
    groovy localGroovy()
}

//将插件部署到repo目录下
uploadArchives {
    repositories.mavenDeployer {
        repository(url: uri('../repo'))
    }
}
  1. 发布:和其他Module一样发布到中央库中
  2. 使用插件,主项目中配置如下:
apply plugin: 'timePlugin'

buildscript {
    repositories {
        maven {
            url uri('../repo')
        } }
    dependencies {
        classpath group: 'com.ray.plugin', name: 'timePlugin',
                version: '1.0'
    }
}

最后

以上就是总结的Gradle实用的进阶指南,让我们可以更加随心所欲地去管理我们的工程。以后如果有一些新的认识或者想法,我也会在这里实时更新的。也算是自己对Gradle认识和学习的总结整理吧。
正确使用Gradle的配置是为了让我们开发更加便捷、效率更高,千万不要本末倒置了。
文中哪里有错误的话,欢迎大家指出纠正
如果文章对你有用的话,请点赞鼓励一下哈O(∩_∩)O~~~~

参考和感谢以下博文和项目:
- 《Android群英传-神兵利器》-徐宜生
- Gradle for Android 第一篇( 从 Gradle 和 AS 开始 )
- Gradle学习总结——根本上看透Android Studio构建
- “替你”总结的Gradle配置
- 分类整理res/Layouts中的布局文件(创建子目录)
- Evernote SDK for Android

发表评论

电子邮件地址不会被公开。 必填项已用*标注