AndroidGradle权威指南__读书笔记

实例代码

https://github.com/rujews/android-gradle-book-code

查看Gradle版本

gradle -v

Hello world

1.创建目录
2.gradle init
3.在build.gradle中
task sayHello <<{
    print"Hello World"
}
//-q 控制日志的输出级别
4.gradle -q sayHello

生成Wrapper

gradle wrapper
//指定gradle版本,通过影响distribution-url来实现修改
gradle wrapper --gradle-version 2.4
//指定版本的url
gradle wrapper --gradle-distribution-url

生成的目录结构
AndroidGradle权威指南__读书笔记_第1张图片

//gradle-wrapper.jar
Gradlew通过该jar来实现Gradle操作

//gradle-wrapper.properties
#Fri Mar 02 10:48:45 CST 2018 //#注释
distributionBase=GRADLE_USER_HOME //环境变量->gradle的安装目录
distributionPath=wrapper/dists //相对路径
zipStoreBase=GRADLE_USER_HOME //同distributionBase,不过是存放zip
zipStorePath=wrapper/dists //同distributionBase,不过是存放zip
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip//指定gradle版本,如果把bin改为all,就可以看到gradle的源码

自定义Wrapper Task

task wrapper(type:Wrapper){
    gradleVersion = '2.4'
}

AndroidGradle权威指南__读书笔记_第2张图片

Gradle日志

ERROR   
QUIET 重要信息
WARNING 
LIFECYCLE 进度,生命周期
INFO
DEBUG

//通过在命令行中添加参数控制
gradle -q taskName
gradle --quiet taskName

打印错误堆栈信息

//关键堆栈,推荐
gradle -s taskName
gradle --stacktrace
//全部堆栈
gradle -S taskName
gradle --full-stacktrace taskName

Logger打印

//除了Object.print(msg),还可以调用Project.logger来打印信息
logger.quiet("");
logger.xxx("");

查询命令

gradle -?
gradle -h
gradle --help 

查看所有可执行的Task

gradle tasks

GradleHelp,查看某个task的帮助

gradle helo --task taskName

强制依赖刷新

我们一个功能不可避免的会依赖很多第三方库,但库不可能每次都进行下载,所以库是有缓存的.
//强制刷新
gradle --refresh-dependencies assemble

多任务调用

gradle taskName1 taskName2

缩写调用

//驼峰命名法
task cleanPicture{}
//首字母缩写
gradle cp

String

//'' 不能动态打印
print'x=${x}' //"x=${x}"
//""可以动态打印,{}中可以放表达式,只有一个变量时可以省略花括号
print'x=${x}' //"x=1"
print'x=$x'  //"x=1"
//''' '''支持任意换行

集合

Groovy完全兼容了Java的集合,并且进行了扩展
List

def list=[1,2,3,4,5]
println list[-1] //倒数第一个元素
println list[1..3]
list.each{  
}

Map

def map=['key1':1,'key2':2]
print map['key1']
map.each{
    //it:Map.Entry
}

方法的编写以及调用

task testMethod <<{
//()可以省略
    def x =method 1,2
    println x
}
def int method(int a,int b){
    //有返回值是可以省略return 会默认把最后依据作为返回值
    a+b
}

闭包

task testC << {
    method (10){
        a,b->
        println a
        println b
    }
}
def method(int a,closure){
    a=a*a
    closure(a,a)
}

闭包作为参数

list.each({
    it.xxx  
})
//最后一个参数是闭包,可以放到外面
list.each(){
    it.xxx  
}
//括号可以省略
list.each{
    it.xx
}

JavaBean

Javabean.属性来直接获取和设置

闭包的委托

Groovy的强大之处是在于它支持方法的委托.
闭包具有三个属性:
thisObject
owner
delegate
在闭包内调用方法时,可以指定通过哪个对象来处理.
默认情况下delegate与owner是相等的
但delegate是可以被修改的
//thisObject的优先级最高,thisObject其实就是构建这个脚本的上下文,它和脚本的中this是相等的
thisObject>owner>delegate

Demo

task testC << {
    testPerson{
        name="小黄"
        age=18
        dumpPerson()
    }
}
class Person{
    String name;
    int age;
    void dumpPerson(){
        println "$name : $age"
    }
}
def testPerson(Closure<Person> closure){
    def p=new Person();
    closure.delegate=p;
    closure.setResolveStrategy(Closure.DELEGATE_FIRST);
    closure(p)
}

Setting文件

Setting.gradle,用于初始化和工程树的配置.

//定义了两个子项目,并为他们指定了目录的位置,如果不配置则默认是在统计的目录综合那个
rootProject.name=''
include':example02'
project(':example02').projectDir=new File(rootDir,'chapter01/example2')
include':example03'
project(':example03').projectDir=new File(rootDir,'chapter01/example3')

每个Project都会有一个Build文件,RootProject也不例外.
RootProject可以获取到所有的子项目,所以我们可以在RootProject的build中为所有子项目统一配置,比如应用的插件,依赖的maven中心等等

subproject{
    apply plugin:'java'
    repositories{
        jcenter()
    }   
}
allprojects{

}

Task

创建一个Task

//其实是project.task(String taskName,Closure<> closure)
task cc {
    println "in create task"
    doFirst{
        println "doFirst"
    }
    doLast{
        println "doLast"
    }
}

任务的依赖

task cc {
    //println "cc mid"

    doFirst{
        println "cc doFirst"
    }
    doLast{
        println "cc doLast"
    }
}
//单个依赖
task bb(dependsOn:cc){
    doFirst{
        println "bb doFirst"
    }
    doLast{
        println "bb doLast"
    }
}
//多个依赖  
task ccc{
    //println "ccc mid"
    dependsOn cc,bb
    doFirst{
        println "ccc doFirst"
    }
    doLast{
        println "ccc doLast"
    }
}
脚本语言是先声明后使用
通过任务名操纵任务的原理是:Project在创建任务的时候同时把该任务

自定义属性

Project和Task都允许用户添加额外的自定义属性;
要添加多个自定义属性可以通过代码块实现;

//project的属性
ext.name="xiaohuang"
//闭包实现多个属性的添加
ext{
    age=18
    num=18
}
task printExt<<{
    //task也有个name属性,就是TaskName,所以需要
    println "age=$age,name=$project.name,num=$num"
}

相比局部变量,自定义属性有更广大的作用范围,你可以通过跨Project和跨Task来访问这些属性;
自定义还可以应用在SourceSet中,使用productFlavors来定义多个渠道的时候,除了main SourceSet还会新增很多SourceSet

apply plugin:"java"
sourceSets.all{
    //为sourceSets.中的每一个sourceSet初始化一个sourceDir属性
    ext.sourceDir=null;
}
sourceSets{
    //创建多个sourceSet,并赋予属性
    main{
        sourceDir="mianDir"
    }
    test{
        sourceDir="testDir"
    }
}
task pp<<{
    sourceSets.each{
        println it.sourceDir
    }
}

一般可用于自定义版本号和版本名称,我们会把它放在一个独立的gradle文件中,便于管理

脚本即代码,代码即脚本

虽然Gradle是脚本,但我们需要时刻记得我们的写的都是代码,
所以我们可以使用Groovy Java Gradle的任何语法和api实现我们想做的事

def String getTime(){
    def date=new Date();
    def dateString =date.format("yyyy-MM-dd")
    dateString
}
task pp<<{
    sourceSets.each{
        println it.sourceDir +getTime()
    }
}

Gradle任务

多种方式创建Gradle任务

在Gradle中我们可以有多种方式在创建任务

//1.直接以一个任务的名字创建任务
def Task myTask1=task(myTask1Do)
myTask1Do.doLast{
    println "myTask1Do.doLast"
}
//2.以一个任务的名字+一个对该任务的配置map来创建task实例
def Task myTask2=task(myTask2Do,group:BasePlugin.BUILD_GROUP)
myTask2Do.doLast{
    println "myTask2Do=$myTask2Do.group"
}
//3.闭包方式
task myTask3Do{
    //闭包中的委托对象是task,description是task的属性
    description '描述'
    doLast{
        println "myTask3Do:$description"
    }
}

多种方式访问task

//1.task在创建时,会作为project的属性添加到project上,所以我们可以通过任务名字来定义和使用task
task mytask
//[]在Groovy是操作符,是getAt()的重载
tasks["mytask"].doLast{
    println"mytask"
}

//2.通过路径访问
task.findByPath(":多方式访问Task:mytask").doLast{
    println "mytask.findByPath" //找不到返回null
}
task.getByPath(":多方式访问Task:mytask").doLast{
    println "mytask.findByPath" //找不到抛出UnKnownTaskException
}
//当我们拿到task的引用的时候,就可以按照我们的业务逻辑去操纵它,比如配置任务依赖,配置一些属性

任务的分组和描述

任务的分组其实就是对任务的分类,便于我们对任务进行归类整理;
任务的描述其实就是说明这个任务有什么用;

//建议在创建任务的时候对这两个属性都要配置
task myTask{
    description "description的demo"
    group=BasePlugin.BUILD_GROUP
    doLast{
        println"description:$description,group=$group"
    }
}
//当我们使用gradle tasks查看任务的时候就可以发现该任务被分类到BuildTasks中去了
//使用IDE似乎鼠标悬停到任务上也可以看到描述

操作符的重载

我们都知道<< 和doLast的效果是相同的,但是为什么呢?
task <

//那么为什么left.shift的效果和doLast相同呢?
//源码:
public Task doLast(final Closure action){
    hasCustomActions=true;
    if(action==null)
        throw new InvalidUserDataException("Action must not be null")
    taskMutator.mutate("Task.doLast(Closure)",new Runnable(){
        public void run(){
            action.add(convertClosureToAction(action))
        }
    })  
}
public Task leftShift(final Closure action){
    hasCustomActions=true;
    if(action==null)
        throw new InvalidUserDataException("Action must not be null")
    taskMutator.mutate("Task.leftShift(Closure)",new Runnable(){
        public void run(){
            action.add(convertClosureToAction(action))
        }
    })  
}
//可以发现这两个方法的关键都是actions.add(),所以他们的效果其实都是一样的

任务的执行流程

指定Task其实就是遍历执行actions List
@TaskAction标齐的方法会被作为action,然后通过task的prependParallelSafeAction被放到actionList中

Task mytask= task mytask1(type:CustomTask)
mytask.doFirst{
    println"doFirst"
}
mytask.doLast{
    println"doLast"
}

class CustomTask extends DefaultTask{
//TaskAction注解表明是主体方法,只能在类中的定义主体方法
    @TaskAction
    def doSelf(){
        println "doseft"
    }
}

任务的排序

通过task.shouldRunAfter
task.mustRunAfter来控制任务的执行顺序

task mytask1 <<{
    println "mytask1"
}
task mytask2 <<{
    println "mytask2"
}
//依赖的顺序不当的话
//Circular dependency between the following tasks:

//强制要求
mytask1.mustRunAfter mytask2
//非强制要求,不一定会按照该顺序执行
mytask2.shouldRunAfter mytask1

任务的禁用和启用

task中有一个enable属性,默认true,为false时,执行该方法会提示该任务被跳过

task mytask<<{
    println"mytask"
}
mytask.enabled=false //SKIPPED

Task.onlyIf(Closure),该闭包返回false则跳过

final String ALL="all"
final String MAIN="main"
final String OTHERS='other'

project.ext{
    build_apps=ALL
}

task yingyongbao <<{
    println "打应用宝的包"
}
yingyongbao.onlyIf{
    def flag=true
    if(project.hasProperty("build_apps")){
        Object buildType=project.property("build_apps")
        if(ALL.equals(buildType)||MAIN.equals(buildType)){
            flag=true
        }else{
            flag=false
        }
    }
    flag
}

task huawei << {
    println "打华为的包"
}
huawei.onlyIf{
    def flag=true
    if(project.hasProperty("build_apps")){
        Object buildType=project.property("build_apps")
        if(OTHERS.equals(buildType)||ALL.equals(buildType)){
            flag=true
        }else{
            flag=false
        }
    }
    flag
}

task sixty <<{
    println "打360的包"
}
sixty.onlyIf{
    def flag=true
    if(project.hasProperty("build_apps")){
        Object buildType=project.property("build_apps")
        if(OTHERS.equals(buildType)||ALL.equals(buildType)){
            flag=true
        }else{
            flag=false
        }
    }
    flag
}
task build 


build.dependsOn yingyongbao,huawei,sixty

任务的规则

我们创建的任务都在TaskContainer中,是由其进行管理的.
TaskContainer继承于NamedDomainObjectCollection,NamedDomainObjectCollection是唯一一个具有唯一不变名字的域的对象的集合,它里面所有的元素都具有唯一不变的名字:String,所以我们可以通过名字获取该元素.


规则的作用:
当查找不到我们要查找到的任务的时候,就会调用我们添加的规则来处理这种异常情况
源码可知,通过addRule(String,Closure)来配置规则.
当我们执行依赖一个不存在的任务时,Gradle会执行失败,通过编写规则我们可以改成打印提示信息


Gradle插件

把插件应用到你的项目中,插件会扩展项目的功能,帮助你在项目的构建过程中做很多事情
1.添加添加任务到你的项目中,如测试 编译 打包
2.可以添加依赖配置到你的项目中,可以配置我们的项目在构建过程中需要的依赖
3.可以想项目中现有的对象添加新的属性 方法等等,实现配置/优化构建,如android{}这个配置块就AndroidGradle插件为peoject对象添加的一个扩展
4.可以对项目进行一些约定,比如应用java插件后,约定src/main/java目录下就是我们源码存储的位置

如何应用一个插件


应用二进制插件

二进制插件就是实现了org.gradle.api.Plugin接口的插件,他们可以有id属性
//gradle自带的核心插件都有一个短名方便记忆
apply plugin:’java’
//对应对应的是
apply plugin:org.gradle.api.plugins.JavaPlugin
//有因为org.gradle.api.plugins是默认导入的,所以可以缩写为
apply plugin:JavaPlugin


应用脚本插件
apply from:'version.gradle'
task printVersionCode << {
    println "VersionCode=$versionCode"
}
//version.gradle
ext{
    versionCode ="1.0.0"
    versionName="XXX"
}

应用第三方插件

第三方的二进制插件,我们应用的时候需要先在buildscript{}中配置classpath

buildscript{
    repositories{
        jcenter();
    }
    dependencies{
        classpath 'com.android.tools.build:gradle:1.5.0'
    }
}
//配置之后就可以应用插件了,否则会提示找不到插件
apply plugin:'com.android.application'

应用plugins DSL插件

//2.1版本就增加的,看起来更简洁,更符合DSL规范
plugins{
    id 'java'
}
如果该插件已经被托管https://plugins.gradle.org/
plugins{
    id 'org.sonarqube' version"1.2"
}

Project.apply()的其他使用方法

void apply(Map<String,?> options)

//该闭包用来配置ObjectConfigurationAction对象,委托对象就是它
void apply(Closure closure)
apply{
    plugin 'java'
}

//类似java回调的方式来实现回调
void apply(Action action)
apply(new Action(){
    void execure(ObjectConfigurationAction objectConfigurationAction){
        objectConfigurationAction.plugin('java')
    }
})

自定义插件

自定义插件必须实现Plugin接口,这个接口只有一个apply(),该方法在插件被应用的时候执行.
一般用于配置一些信息

class myPlugin implements Plugin<Project>{
    void apply(Project project){
        project.task('myPluginTask')<<{
            prinlnt"myPlugin"
        }
    }
}
apply plugin:myPlugin 

Gradle Java插件

Java开发流程基本都差不多,无非就是依赖第三方库,编译源文件,进行单元测试,打包发布等等;
所以Gradle为了让我们节省时间,提供了非常核心的java插件

应用Java插件

apply plugin:'java'
java插件会为工程添加很多默认的设置和约定,比如源代码的位置,单元测试代码的位置,资源文件的位置

Java插件的java项目结构

java插件约定src/main/java为源代码位置;
src/main/resource要打包的文件的存放目录
src/test/java单元测试
src/test/resource单元测试的文件

project
    build.gradle
    src
        main
            java
            resource
        test
            java
            resource    

自定义配置

main和test是Java插件为我们内置的两个源代码集合

//新增vip版本的版本
apply plugin:'java'
sourceSets{
    vip{
    }
    main{
        java{
            srcDir'src/java'
        }
        resource{
            srcDir'src/resource'
        }
    }
}

配置第三方依赖

//需要先配置jar仓库
//还可以从jcenter库,ivy库,本地Maven库mavenLocal
repositories{
    mavenCentral()
    maven{
        url"http://www.mavenurl.com"
    }
}

dependencies{
    compile group:'com.squareup.okhhtp3',name:'okhttp',version:'3.0.1'
    compile 'com.squareup.okhhtp3':'okhttp':'3.0.1' 
}

Gradle的依赖配置

名称 继承自 被哪个任务使用 作用
compile - compileJava 编译时依赖
runtime compile - 运行时依赖
testCompile compile compileTestJava 编译测试用例时依赖
testRuntime runtime,testCompile test 仅仅在测试用例运行时依赖
archives - uploadArchives 该项目发布构件(Jar包等)依赖
default runtime - 默认依赖配置

另外Java插件可以在不同的源集在编译时和运行不同的依赖

dependencies{
    mainCompile''
    vipCompile''
}
名称 继承 被哪个任务使用 作用
sourceSetCompile compileSourceSetJava 为指定的源集提供编译时依赖
sourceSetRuntime sourceSetCompile 为指定的源集提供的一年行驶依赖

dependencies{
    //依赖项目
    compile project(':projectName')
    //依赖文件
    compile files('libs/xx.jar','libs/xx2.jar')
    //依赖文件夹
    compile fileTree(dir:'libs',include:'*.jar')
}

如何构建一个Java项目

SourceSet

SourceSet,Java插件用来描述和管理源代码及其资源文件的一个抽象概念,是一个Java源代码文件和资源文件的集合.
通过SourceSet可以非常方便地访问源代码目录,设置源码的属性,更改源集的Java目录或资源目录等
有了源集,我就针对不同的业务和应用对我们源代码进行分组,比如main test,它们是Java插件默认内置的两个标准源集.

apply plugin:'java'
sourceSets{
    main{
    }
    test{
    }
sourceSets.all{
}
sourceSets.each{
}

740

属性名 类型 描述
name String 描述
output.classesDir File 该源集编译后的class文件目录
output.resourceDir File 编译后生成的资源目录
compileClasspath FileCollection 编译该源集合所需的classpath
java SourceDirectorySet 该源集的Java源文件
java.srcDirs Set 该源集的Java源文件所在目录
resources SourceDirectorySet 该源集的java文件
resource.srcDirs Set 该源集的资源文件的所在目录
//使用方法
sourceSets{
    main{
        java{
            srcDir 'src/java'
        }
        resource{
            srcDir 'src/resources'
        }
    }
}

Java插件的Task

任务名称 类型 描述
compileJava JavaCompile 使用javac编译Java文件
proecssResources Copy 把资源文件拷贝到资源文件目录里
classes Task 组建产生的类和资源文件目录
compileTestJava JavaCompile 使用java编译测试Java源文件
proecssTestResource Copy 把测试资源文件复制到生产的资源文件目录里
testClasses Task 组建产生的测试类和相关资源目录
javadoc Javadoc 使用javadoc生成javaapi文件
test Test 使用Junit或TestNG单元测试
uploadArchives Upload 上传的Jar的构建,用archives{}配置
clean Delete 清理构件生成的目录文件
cleanTaskName Delete 删除指定任务生成的文件,比如cleanJar删除的Jar任务生成的
compileSourceSetjava JavaCompile 使用javac编译指定源集的Java源代码
proecssSourceSetResources Copy 把指定源集的资源文件复制到生产文件的资源目录下
sourceSetClasses Task 组装指定源集和类和资源文件目录

ps:sourceSet ->实际的源集名称

Java插件添加的属性

属性名 类型 描述
sourceSets SourceSetContainer 该项目的源集,可以访问和配置源集
sourceCompatibility JavaVersion 编译该Java文件使用的Java版本
targetCompatibility JavaVersion 编译生成的类的Java版本
archivesBaseName String 我们打包成Jar或Zip文件的名称
manifest Manifest 用于访问或者配置我们的manifest清单文件
libsDir File 存放生成的类的目录
distsDir File 存放生成的发布的文件的目录

多项目构建

//Setting.gradle
include':project1'
project(':project1').projectDir=new File(rootDir,'path')
//要使用其他项目的类,需要依赖
dependencies{
    compile project(':xx')
}
//在跟project中对其他所有子项目进行配置
subprojects{
    apply plugin:'java'
    repositories{
        mavenCentral()
    }
    dependencies{
    }
}

AndroidGradle插件

AndroidGradle插件其实就是一个Gradle的第三方插件

AndroidGradle插件分类

//app
com.android.application
//aar
com.android.library
//test
com.android.test

应用Andriod插件

buildscript{
    repositories{
        jcenter()
    }
    dependencies{
        classpath'com.android.tools.build:1.5.0'
    }
}
apply plugin:'com.android.application'
//android{}是Android插件的提供的一个扩展类型,用于自定义AndroidGradle工程
android{
    compileVersion 23
    buildToolsVersion "23.0.1"
}

工程结构

project
    build.gradle
    project.iml
    libs
    proguard-rules.pro
    src
        androidTest
            java
        main
            AndroidManifest.xml //android特有
            java
            res //特有
        test
            java        

android{}是唯一的入口,通过它可以实现对Android Gradle项目进行自定义的扩展其具体实现是com.android.build.gradle.AppExtendsion,是Project的一个扩展

//build.gradle
buildscript{
    repositories{
        jcenter()
    }
    dependencies{
        classpath'com.android.tools.build:gradle:1.5.0'
    }
}
apply plugin:'com.android.application'
android{
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
    defaultConfig{
        applicationId "org.fly.xxx"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes{
        release{
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguardrule.tx'
        }
    }   
}
dependenciese{
}
//getExtensionClass(),在AndroidGradle插件中返回就是com.android.build.gradle.AppExtension
//所以Android的很多配置可以从这个类中去找
extension=project.extendsions.create('android',getExtensionClass()
    ,(ProjectInternal)project,instantiator,androidBuilder,sdkHandler
    ,buildTypeContainer,productFlavorContainer,signingConfigContainer
    ,extraModelInfo,isLibrary())

android 插件属性

compileSdkVersion:
配置我们编译的Android工程SDK,该配置的原型就是是一个compileSdkVersion方法

android{
    compileSdkVersion 23
}
public void compileSdkVersion(int apiLevel){
    compileSdkVersion("android-"+apiLevel)
}

//还有一个重载方法
public void compileSdkVersion(String version){
    chekWriteability()
    this.target=version
}
android{
    comileSdkVersion 'android-23'
}

//还有一个set方法,所以我们可以把他当成一个属性使用
android.compileSdkVersion=23
android.compileSdkVersion='android-23'

public void setCompileSdkversion(int level){
    ...
}
public void setCompileSdkversion(String level){
    ...
}

buildToolsVersion:

//常用方法
public void buildToolsVersion(String version){
    checkWritability();
    buildToolsRevision=FullRevision.parseRevision(version);
}
//同样有set方法
public String getBuildToolsVersion(){
    return buildToolsRevision.toString();
}
public void setBuildToolsVersion(String version){
    buildToolsVersion(version);
}

defaultConfig:
defaultConfig是一个ProductFlavor,具有默认的配置,ProductFlavor允许我们根据不同的情况生成多个不同的APK包,比如我们的多渠道包.
如果不针对我们自定义的ProductFlavor单独配置,会为这个ProductFlavor使用默认的defaultConfig配置.

参数 作用
applicationId 配置我们的包名
minSdkVersion 最低支持的安卓版本
targetSdkVersion 基于哪个安卓版本开发的
versionCode 版本号
versionName 版本名称

buildTypes:
buildTypes,一个NamedDomainObjectContainer,与SourceSet类型是一个域对象.
SourceSet中有main/test,同样的,buildTypes中有release,debug等等.
我们可以在buildTypes{}里新增任意多个我们需要构建的类型

名称 意义
release BuildType类型
minifyEnable 是否开启混淆
proguardFiles proguard的配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rule.pro'

AndroidGradle任务

Android插件是基于Java插件的,所以Android插件基本包含了所有的Java插件的功能,如assemable,check,build等,
此外还添加了connectCheck deviceCheck lint install uninstall 等等任务

任务名称 作用
connectCheck 在所有连接的设备或模拟器上运行check检查
deviceCheck 通过API连接远程设备运行check,被用于CI(持续集成)服务器上
lint 在所有ProductFlavor上运行lint检查
install 在已经连接的设备上安装应用
uninstall 卸载应用
signingReport 打印App的签名
androidDependencies 打印Android的依赖

一般我们常用的build assemable clean lint check,通过这些任务可以打包生成apk,对现有的Android进行lint检查


从Eclipse迁移项目到AndroidStudio

p93


自定义AndroidGradle工程

defaultConfit默认配置

defaultConfig是Android对象中的一个配置块,负责定义所有的默认配置.他是一个ProductFlavor,如果一个ProductFlavor没有特殊定义配置,默认会使用defaultConfig{}指定配置

android{
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
    defaultConfig{
        applicationId "com.xx.app.xx"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
}

applicateId

applicationId是ProductFlavor的一个属性,用于指定app的包名,默认是null.
为null时,在构建过程中会从AndroidManifest.xml中读取,
manifest标签的package属性


minSdkVersion

是ProductFlavor的一个方法

public void minSdkVersion(int minSdk){
    setMinSdkVersion(minSdk);
}
public void setMinSdkVersion(@Nullable String minSdkVersion){
    setMinSdkVersion(getApiVersion(minSdkVersion))
}
public void MinSdkVersion(@Nullable String minSdkVersion){
    setMinSdkVersion(getApiVersion(minSdkVersion))
}

versionCode

ProductFlavor的一个属性,配置AndroidApp的内部版本号.没有配置时从AndroidManifet.xml中读取

public ProductFlavor setVersionCode(Integer version){
    mVersionCode=versionCode;
    return this;
}
public Integer getVersion(){
    return mVersionCode;
}

versionName

versionName和versionCode类型,也是ProductFlavorde一个属性,用于让用户知道我们的应用的版本.

public ProductFlavor setVersionName(String version){
    mVersion=versionName;
    return this;
}
public String getVersionName(){
    return mVersionName;
}

testApplicationId

用于配置测试App的包名,默认情况是applicateionId+”.test”.
一般情况下默认即可,它也是ProductFlavor的一个属性

public ProductFlavor setTestApplicationId(String applicationId){
    mTestApplicationId=applicationId;
    return this;
}
public String getTestApplicationId(){
    return mTestApplicationId;
}

testInstrumentationRunner

用于配置单元测试用的Runner,默认使用的是android.test.InstumentationTestRunner,如果想使用自定义的配置,
修改该值即可

public ProductFlavor setTestInstrumentationRunner(String testInstructmentationRunner){
    mTestInstructmentationRunner=testInstructmentationRunner
    return this;
}
public String getTestInstructmentationRunner(){
    return  mTestInstructmentationRunner;
}

SigningConfig

配置默认的签名信息,对生成的app签名.
也是ProductFlavor的一个属性,可以直接对其进行配置

public SingingConfig getSigningconfig(){
    return mSigningConfig;
}
public ProductFlavor setSigningConfig(SigningConfig signConfig){
    mSigningConfig=signConfig
    return this;
}

proguardFiles

配置混淆文件,可以接收多个文件

public void proguardFile(Object ..proguardFileArray){
    getproguardFiles().addAll(project.files(proguardFileArray.getfiles()));
}

配置签名信息

一个app只有签名之后才能被发布 安装 使用 ,签名是保护app的方式,标记该app的唯一性.如果app被恶意篡改,签名不一样了,那么该app就无法升级安装.
app有debug release两种模式:
debug,Android SDK为我们提供了一个默认的debug签名
release,debug无法发布,所以我们要配置自己的签名

/*
singingConfigs是android的一个方法,接口一个域对象(NamedDomaimobjectContainer)作为其参数
,所以我们在signingConfigs{}中定义的都是一个SignConfig.
*/
android{
    ...
    signingConfigs{
        release{
            storeFile file("xx.keystore")
            storePassword "passwort"
            keyAlias "MyReleaseKey"
            keyPassword "password"
        }
        //debug签名一般不手动配置,已经有默认配置了
        debug{
            storeFile file("$HOME/.android/debug.keystore")
            storePassword "password"
            keyAlias "MyDebugKey"
            keyPassword "password"          
        }
    }
    //配置好之后就可以进行引用了
    defaultConfig{
        applicationId "com.xx.app.projectName"
        minSdkVrsion 23
        targetTargetSdkVersion 25
        versionCode 1
        versionName "1.0"
        signingConfig signingConfigs.debug
    }
    //还可以针对类型分别配置签名信息,例如对vip版本特别配置vip签名
    buildTypes{
        release{
            signingConfig signingConfigs.release
        }
        debug{
            signingConfig signingConfigs.debug
        }
    }
}

构建的应用类型

debug 和realse的区别在于能否在设备上调试,以及签名不同.
其他代码和资源文件都是一样的

android{
    buildTypes{
        release{
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt')
            ,'proguardrules.pro'
        }
        debug{
            ...
        }
        //新增类型很简单,因为buildTypes也是一个NamedDomainObjectContainer
        vip{

        }
    }
}

BuildType属性

每一个BuildType都会生成一个SourceSet,默认为src//.

每一个SourceSet包含源代码 资源文件等信息.
所以针对不同的BuildType我们可以单独为其指定Java源码 res资源等.

每一个BuildType都会新增一个SourceSet所以注意不要命名为main和mainTest,因为已经被系统占用

除了生成对应的SourceSet外,每一个BuildType还会生成assemable任务.
常用的asssemableRelease和assemableDebug就是Gradle(release,debug)自动生成的任务.
assemableXXX就能生成对应的apk文件


applicationIdSuffix:
用于配置基于默认的applicationId的后缀,
如:
applicationId “com.xx.app.projectName”
applicationIdSuffix “.debug”
生成的apk包名为 “com.xx.app.projectName.debug”

public BaseConfigImpl setApplicationIdSuffix(String applicationIdSuffix){
    mApplicationIdSuffix ==applicationIdSuffix
    return this;
}
public String getApplicationIdSuffix(){
    return mApplicationIdSuffix;
}

debuggable:
配置是否生成一个debug的apk,类型为boolean

public BuildType setDebuggable(Boolean debuggable){
    mDebuggable=debuggable;
    return this;
}
public boolean isDebuggable(){
    return mDebuggable||mTestConverageEnabled;
}

jniDebuggable:
与Debuggable类似,配置是否生成jni代码的apk

public BuildType setJniDebuggable(boolean jniDebuggable){
    mJniDebuggable = jniDebuggable;
    return this;
}
public boolean isJniDebuggable(){
    return jniDebuggable;
}

minifyEnabled:
配置是否开启混淆

public BuildType setMinifyEnable(boolean enable){
    mMinifyEnable=enable;
    return this;
}
public boolean isMinifyEnabled(){
    return mMinifyEnabled;
}

mutilDexEnable:
配置是否开启MutilDex

public void setMutilDexEnabled(boolean enabled){
    mUtilDexEnabled=enable;
}
public boolean isMutilDexEnabled(){
    return mMutilDexEnabled;
}

proguardFile:
配置混淆规则的文件

public BuildType proguardFile(@NonNull Object proguardFile){
    getProguardFiles().add(project.file(proguardFile));
    return this;
}

proguardFiles:
一次配置多个混淆文件

public BuildType proguardFiles(@NonNull Object.. proguardFiles){
    getProguardFiles().add(project.files(proguardFiles).getFiles());
    return this;
}

shrinkResources
配置是否自动移除未使用的资源文件,默认为false

public void setShrinkResource(boolean shrinkResource){
    this.shrinkResource=shrinkResource;
}
public void isShrinkResource(){
    return shrinkResource;
}

signingConfig
配置签名默认设置

public BuildType setSigningConfig(SigningConfig signingConfig){
    mSigningConfig=signingConfig
    return this;
}
public SifningConfig getSigningConfig(){
    return mSigningConfig;
}

混淆

配置混淆

android{
    buildTypes{
        //仅发布版开启混淆
        release{
            minityEnabled ture
            //传入文件名称,获取AndroidSdk中默认的混淆文件(tools/proguard/xx.txt)
            proguardFiles getDefaultProguardFile('proguard-android.txt')
            ,'proguard-rules.pro'
        }
        //因为混淆后就无法断点调试了
        debug{
        }
    }
}

编写混淆规则

//AndroidSdk默认有两个混淆文件
//1.proguard-android.txt
//2.proguard-android-optimize.txt,优化过的
public File getDefaultProguardFile(String name){
    File sdkDir=sdkHandler.getAndCheckSdkFolder();
    return new File(sdkDir,SdkConstants.FD_TOOLS+File.separatorChar
        +SdkConstants.FD_PROGUARD+File.separatorChar+name)
}

启用zipalign优化

一个整理优化Apk文件的工具,推荐开启

android{
    buildTypes{
        release{
            zipAlignEnabled true
        }
    }
}

使用共享库

Android的包(比如android.app android.content android.view android.widge等)是默认就包含在Android SDK中的,系统会帮我们自动链接它们;
但有些库是需要我们去AndroidManifest.xml中配置后才能使用(如com.google.android.maps android.test.runner)等,需要单独去生成,这些库被称为共享库

//声明需要使用共享库后,在安装时需要手机系统没有该共享库,那么该应用不能被安装
<uses-library
    android:name="com.google.android.maps"
    android:required="true"/>

在Android系统中,除了标准的AndroidSDK,还有两种库:
1.add-ons库:
位于add-ons目录下,一般是第三方公司开发的,为了让开发者们使用但又不想暴露具体实现;
AndroidGradle会自动解析,添加到classpath中
2.optional库
位于platforms/android-xx/optional目录下,一般是为了兼容旧版本.(如org.apache.http.legacy是httpClient库,api23后sdk移除了该库,如需要则必须使用可选库)
不会自动解析并添加到classpath中,所以需要我们手动解析

//仅仅是为了保证编译通过
//最好在AndroidManifest.xml中也要配置
//PackageManager().getSystemSharedLibraryNames();
android{
    useLibrary 'org.apache.http.legacy'
}

批量修改生成的apk文件名称

Andoird工程相对Java工程来说,要复杂的多,因为它有很多相同的任务,这些任务的名称是通过BuildTypes和ProductFlavors动态创建和生成的(通过project.tasks无法获取任务,因为还无生成).

为了解决这个问题,Android对象提供了三个属性,这三个属性都是DomainObjectSet对象集合
1.applicationVariants
仅适用于Android应用插件
2.libraryVariants
仅适用于Android库Gradle插件
3.testVariants
以上两种都适用
注意这三种集合都会触发创建所有的任务,这以为着访问这些集合后不需要重新配置就会产生

public DomainObjectSet getApplicationVariants(){
    return applicationVariantList;
}

实现修改apk文件的需求

android{
    ...
    useLibrary 'org.apache.http.legacy'
    buildTypes{
        realeas{
        }
    }
    productFlavors{
        google{
        }
    }
    applicationVariants.all{
        variant->
        variant.outputs.each{
            output->
            if(output.outputFile!=null && output.outputFile.name.endsWith('.apk')
                && 'release'.equals(variant.buildType.name)){
                    println "variant:${variant.name}___output:${output.name}"
                    def file = new File(output.outputFile.parent,"my_${variant.name}.apk")
                    output.outputFile=file
                }
        }
    }
}

applicationVariants是一个DomainObjectCollection集合,通过all()遍历,遍历的每个variant是一个生成的产物,
生成数量为productFlavor * buildType 个.
applicationVariant具有一个outputs作为它的输出,outputs是一个List集合

动态生成版本信息

设置版本信息

在build中配置,但是不方便修改

android{
    ...
    deaultConfig{
        ...
        versionCode 1
        versionName "1.0.0"
    }
}

分模块设置版本信息

//version.gradle
ext{
    appVersion=1
    appVersionName="1.0.0"
}
//build.gradle
apply from:'version.gradle'
android{
    ...
    defaultConfig{
        ...
        versionCode appVersion
        appVersionName appVersionName
    }
}

从Git的tag中获取

//git 中获取tag的命令
git describe --abbrev=0 --tags

在Gradle中执行Shell命令

//推荐
ExecResult exec(Closure closure);
ExecResult exec(Action super ExecSpec> action);
//闭包委托给ExecSpec
public interface ExecSpec extends BaseExecSpec {
    void setCommandLine(Object... args);
    void setCommandLine(Iterable> args);
    ExecSpec commandLine(Object... args);
    ExecSpec commandLine(Iterable> args);
    ExecSpec args(Object... args);
    ExecSpec args(Iterable> args);
    ExecSpec setArgs(Iterable> args);
    List getArgs();
}
//定义一个方法
def getAppversion(){
        def os = new ByteArrayOutputStream()
    exec{
        //亲测不行,找不到名称,但其他命令可以
//        commandLine 'git','describe','--abbrev=0','--tags'
//        commandLine 'git','status'
        standardOutput=os
    }
    return "mytask:"+os.toString()
}

//使用该方法
android{
    defaultConfig{
        versionName getAppversion()
    }
}




task mytask <<{
    def os = new ByteArrayOutputStream()
    exec{
        //亲测不行,找不到名称
//        commandLine 'git','describe','--abbrev=0','--tags'
//        commandLine 'git','status'
        standardOutput=os
    }
    println "mytask:"+os.toString()
}

隐藏签名文件信息

保存到服务器中,以环境变量的方式读取

首先,你得有一个专门打包发版的服务器
并配置对应的环境变量

android{
    ...
    signingConfigs{
        def appStoreFile=System.getenv("STORE_FILE")
        def appStorePassword=System.getenv("STORE_PASSWORT")
        def appKeyAlias=System.getenv("KEY_ALIAS")
        def appKeyPassword=System.getenv("KEY_PASSWORD")

        //当不能从当前环境变量中获取时则使用Debug签名
        //从AndroidSdk(${Home}/.android/)中复制Debug签名到工程目录中
        if(!appStoreFile||!appStorePassword||!appKeyAlias||!appKeyPassword){
            appStoreFile="debug.keystore"
            appStorePassword="android"
            appKeyAlias="androiddebugkey"
            appKeyPassword="android"
        }
        release{
            storeFile file(appStoreFile)
            storePassword appStorePassword
            keyAlias appkeyAlias
            keyPassword appKeyPassword
        }
    }
    buildTypes{
        release{
            signingConfig signConfigs.release
            zipAlignEnabled true
        }
    }
}

动态配置AndroidManifest.xml

在构建过程中动态的修改配置文件

,如 友盟第三方分析统计的时候会要求我们

//AndroidManifest.xml
value="Channel ID" android:name="UMENG_CHANNEL"/>

但配置文件只有一个.

为了解决这个问题,AndroidGradle提供了非常便捷的manifestPlaceholder Manifest占位符.


ManifestPlaceholder是ProductFlavor的一个属性:Map,所以我们可以同时配置多个占位符

android{
    ...
    productFlavor{
        google{
            manifestPlaceholder.put("UMENG_CHANNEL","google")
        }
        baidu{
            manifestPlaceholder.put("UMENG_CHANNEL","baidu")
        }
    }
    //也可以一次性修改
    productFlavor.all{
        flavor->
        manifestPlaceholder.put("UMENG_CHANNEL",name)
    }
}
//在配置文件中是,未验证,但应该不需要在配置文件中写这行
"${UMENG_CHANNEL}" android:name="UMENG_CHANNEL"/>

自定义BuildConfig

BuildConfig是由AndroidGradle编译自动生成的

public final class buildConfig{
    //是否是debug模式
    public static final boolean DEBUG=Boolean.parseBoolean("true")
    //包名
    public static final String APPLICATION_ID="org.flysnow.app.projectName"
    //构建类型
    public static final String BUILD_TYPE="debug"
    //产品风格
    public static final String FLAVOR="baidu"
    //版本号和版本名称
    public static final int VERSION_CODE=1
    public static final String VERSION_NAME="xx.1.0"
}

自定义BuildConfig

android{
    ...
    productFlavors{
        google{
            //注意'""'中的""不能省略,否则生成的类型是String WEB_URL=http://www.google.com
            buildConfigField 'String','WEB_URL','"http://www.google.com"'
        }
        baidu{
            buildConfigField 'String','WEB_URL','"http://www.baidu.com"'
        }
    }
    //因为BuildType也是一种productFlavor,所以...
    buildType{
        debug{
            buildConfigField 'String','NAME','"value"'
        }
    }
}

动态添加自定义的资源

仅针对res/values资源
它们不光可以在res/values.xml中定义,还可以在AndroidGradle中定义.

//product.Flavor.resValue源码
//由注释可知它会生成一个资源,其效果和在res/values文件中定义是一样的
    public void resValue(
            @NonNull String type,
            @NonNull String name,
            @NonNull String value) {
        ClassField alreadyPresent = getResValues().get(name);
        if (alreadyPresent != null) {
            logger.info("BuildType({}): resValue '{}' value is being replaced: {} -> {}",
                    getName(), name, alreadyPresent.getValue(), value);
        }
        addResValue(new ClassFieldImpl(type, name, value));
    }
//demo
android {
    ...
    buildTypes {
        debug {
            zipAlignEnabled true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt')
            , 'proguard-rules.pro'
            //string id bool dimen integer color
            resValue 'string','BaseUrl','http://www.baidu.com'
        }
    }
}
//会在build/generated/res/resValues/baidu/debug/values/generated.xml

    "BaseUrl" translatable="false">http://www.baidu.com
    

Java编译选项

在AndroidGradle中对Java源文件的编码 源文件使用的JDK版本进行修改

android{
    ...
    compileOptions{
        encoding='utf-8'
        sourceCompatibility=JavaVersion.VERSION_1_6
        targetCompatibility=JavaVersion.VERSION_1_6
    }
}

Adb操作选项配置

adb,Android Debug Bridge,用于连接电脑和设备的进行一些调试操作.
在Shell中我们可以通过输入adb来查看其功能和使用说明.
在Gradle中我们也可以有一些配置

android{
    ...
    adbOptions{
        //超时则抛出CommandRejectException
        timeOutInMs 5*1000
        //详情见下图
        setInstallOptions '-r','-s'
    }
}

setInstallOptions

-l:锁定该应用程序
-r:替换已经存在的程序,也就是强制安装
-t:允许测试包
-s:把应用安装到sd卡上
-d:允许进行降级安装
-g:给该应用授权所有运行时的权限
AndroidGradle权威指南__读书笔记_第3张图片


DEX选项配置

Android中的源码被编译成class文件后,在打包成apk文件时又被dx命令优化成Android虚拟机可执行的dex文件.
对于这些dex文件的生成和处理,AndroidGradle会自动调用android SDK的dx命令.

但是有时候也会出现内存不足的异常(java.lang.OutOfMemoryError),因为该命令其实就是一个脚本(dx.jar),由Java程序执行的.
由错误信息可知,默认分配的是G8(1024MB)
我们也可以通过-j参数配置

dexOptions{
    //是否开启增量模式,增量模式速度会更快,但可能会出现很多问题,一般不开启
    incremental true
    //分配dx命令的堆栈内存
    javaMaxHeapSize '1024mb'
    //65536后能构建成功
    jumboMode true
    //配置是否预执行dex Library库工程,开启后会大大加快增量构建的速度,不过clean构建的速度
    //默认true,但有时需要关闭这个选项(如mutil dex)
    preDexLibraries false
    //dx命令时的线程数量
    threadCount 2
}
//源码
public interface DexOptions {
    boolean getPreDexLibraries();
    boolean getJumboMode();
    boolean getDexInProcess();
    boolean getKeepRuntimeAnnotatedClasses();
    String getJavaMaxHeapSize();
    Integer getThreadCount();
    Integer getMaxProcessCount();
    List getAdditionalParameters();
}

解决64K异常

随着业务越来越复杂,特别是集成第三方jar包
因为Dalvik虚拟机使用了short类型做作为dex文件中方法的索引,也就意味着单个dex文件只能拥有65536个方法


首先使用的Android Build Tools和Android Support Repository到21.1
其次在Gradle中开启

//没超过只会有一个dex文件
//开启后会生成class.dex .. calssn.dex
android{
    defaultConfig{
        ...
        multiDexEnabled true
    }
}
//但在5.0前只认一个dex,所以需要在入口中配置
//没有自定义applcation时
"android.support.multidex.MultiDexApplication"
//自定义时则extends MutilDexApplication
//或
public MyApplication extends Application{
    protected void attachBaseContext(Context base){
        super.attachBaseContext(base);
        //MultiDexApplication也是这么实现的
        MutilDex.install(this);
    }
}   

自动清理未使用的资源

使用Android Lint检测没有使用的资源手动删除


Resource Shrinking

在构建时,会检测所有资源,看看是否被引用(不管是不是第三方),没有被引用的资源则不会被打包的apk中.

一般Resource Shrinking要配合混淆使用,混淆时会清理无用代码,这样无用代码引用的资源也会被移除

android{
  ...
  buildTypes{
        release{
        //通过日志输出可以看到哪些文件被清理了
            minifyEnabled true
            shrinkResource true
        }
    }
}

但有时候通过反射引用资源文件的时候,使用到的资源文件也会被删除,所以我们需要保存某些资源文件

//res/raw/keep.xml,该文件不会被打包进apk
"1.0" encoding="utf-8">
"http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used*_b"/>
//keep.xml还有一个属性是tools:shrinkMode,用于配置清理模式
默认safe是安全的,可以识别getResource().getIdentifier("unused","drawable",getPackageName())
如果改成strict则会被删除

resConfigs中配置

使用GoogleMaps时因为国际化的问题,我们可能并不需要其中的某些文件,我们只需要其中一些语言就行了

resConfigs是ProductFlavor的一个方法,它的参数就是我们常用的资源限定符

android{
  ...
  defaultConfig{
        ...
        //打包时仅保留中文
        resConfig 'zh'
        //一次配置多个
        resConfigs{

        }
    }
}

Android Gradle多项目构建

Android项目

一般分为三种:


1.应用项目,com.android.application


2.库项目,com.android.library

和一般的java库非常类似,比Java多得是一些Android特有的资源等;

一般把一些具有公共特性的类 资源可以抽象成一个库project;

如果工程非常复杂,可以根据业务分割成一个个的库项目,然后通过一个主项目引用他们,组合成一个最终app

//默认发布的都是release版本,可以通过配置来改变它
android{
  defaultPublishConfig "debug"
  //或针对风格配置
  defaultPublishConfig "flavorDebug"
  //一般是默认生成一个aar包(false),可以开启多个aar
  publishNonDefault true
}
//引用不同类型的Android库项目
dependencies{
  flavor1Compile project(path:':lib1',comfiguration:'flavor1Release')
  flavor2Compile project(path:':lib2',comfiguration:'flavor2elease')
}

3.测试项目,com.android.test

一般是为了对App进行测试而创建的,比如测试Act Service等,它是Android基于JUnit提供的一种测试Android项目的框架方法

配置多项目

//项目结构
project
    setting.gradle
    app
        build.gradle
    libraries
        lib1
            build.gradle
        lib2
            build.gradle
//setting.build
include ':app',':libraries:lib1',':libraries:lib2'
//指定目录的项目
project(':othersProject').projectDir=new File(rootDir,'others/xx')

引用其他库

dependencies{
    compile project(':libraries:lib1')
}

配置自己的Maven服务器

p150

多渠道构建

因为发布或者推广的渠道不同,所以Android App可能会有很多个.

每个渠道可能有各自的特殊处理,这就需要我们有一套满足多渠道的自动化工具.

Android Gradle 的Product Flavor就是为了解决这个问题

多渠道构建的基本原理

Android Gradle定义了一个Build Variant,由Build Type和 Product Flavor组成(如debug+baidu=baiduDebug产物).

ProductFlavor就是多渠道构建的基础

android{
  productFlavors{
        baidu{
        }
        google{
        }
    }
}

配置了多渠道以后,AndroidGradle就会生成很多Task,基本上都是基于BuildType+ProductFlavor的方式生成(assembleBaidu assembleDebug assembleBaiduDebug ps:assemble生成apk)


Flurry多渠道打包

Flurry是以application划分渠道的,每个application都有一个key,称为flurry key

android{
  ...
  buildTypes{
        debug{
        }
        release{
        }
    }
    productFlavors{
        google{
            buildConfigField 'string','FLURRY_KEY','ASFSDFSDF'
        }
        baidu{
            buildConfigField 'string','FLURRY_KEY','VZXEGSDFA'
        }
    }
}
//在Application中初始化
Flurry.init(this,FLURRY_KEY)

友盟多渠道打包

友盟是通过在配置文件中配置的

android{
    ...
    productFlavor{
        google{
            manifestPlaceholder.put("UMENG_CHANNEL","google")
        }
        baidu{
            manifestPlaceholder.put("UMENG_CHANNEL","baidu")
        }
    }
    //也可以一次性修改
    productFlavor.all{
        flavor->
        manifestPlaceholder.put("UMENG_CHANNEL",name)
    }
}
//在配置文件中是,未验证,但应该不需要在配置文件中写这行
"${UMENG_CHANNEL}" android:name="UMENG_CHANNEL"/>

多渠道定制

多渠道的定制,其实就是对Android Gradle插件的Product Flavor的配置,实现灵活控制每一个渠道的目的

Flavor(风味)

applicationId

ProductFlavor的属性,用于设置该渠道的包名

如果你的App想为该渠道特别配置包名可以通过该属性设置

android{
  ...
  defaultConfig{
        applicationId "org.flysnow.app.xxx"
    }
  productFlavors{
        google{
            applicationId "org.flysnow.app.xxx.google"
        }
    }
}
//源码
public ProductFlavor setApplicationId(String applicationId){
  mApplicationId=applicationId
  return this;
}

comsumerProguardFiles

仅对一个Android库项目,当我们项目生成aar包,使用的comsumerProguardFiles配置的混淆文件列表也会打包到aar中一起发布.

当应用使用该aar和开启混淆的时候,会自动使用该混淆文件,使用者就不需要对该aar包进行混淆了

android{
    productFlavor{
        google{
            //使用consumerProductFiles()会增加文件
            consumerProductFiles 'proguard-rule.pro','proguard-android.txt'
            //使用属性设置会替换
            consumerProductFiles=
        }
    }
}

manifestPlaceholder

占位符

android{
    ...
    productFlavor{
        google{
            manifestPlaceholder.put("UMENG_CHANNEL","google")
        }
        baidu{
            manifestPlaceholder.put("UMENG_CHANNEL","baidu")
        }
    }
    //也可以一次性修改
    productFlavor.all{
        flavor->
        manifestPlaceholder.put("UMENG_CHANNEL",name)
    }
}

mutilDexEnabled


proguardFiles


signingConfig


testApplicationId

我们一般都会对Android进行单元测试,这个单元测试有自己的apk测试包.

testApplicationId就是用来测试包的包名,他的使用方式和我们前面介绍的applicationId一样


testFunctionalTest和testHandlerProfiling

testFunctionalTest 表示是否为功能测试

testHandlerProfiling 表示是否启用分析功能

//他们主要用来控制测试包生成的AndroidManifest.xml
//因为他们最终的配置体现在AndroidManifest.xml文件的instrumentation标签上
android{
    productFlavors{
        google{
            testFunctionalTest true
            testHandleProfiling true
        }
    }
}

testInstrucmentationRunner

用来配置运行测试使用的Instrumentation Runer的类名,是一个全路径的类名.

必须为android.app.Instrumentation的子类.

android{
    productFlavors{
        google{
            testInstrumentationRunner 'android
                .test.InstrumentationTestRunner'
        }
    }
}

testinstrcmentationRunnerArguments

配合上一个属性使用,用来配置Instrumentation Runner使用的参数,其实他们最终使用的都是adb shell am instrument命令.

android{
    productFlavors{
        google{
            testInstrumentationRunnerArguments.put("converage",'true')
        }
    } 
}

versionCode versionName


useJack

配置是否开启新编译器Jack Jill.

现在我们使用的是常规的Android编译器,稳定,但是太慢

但Jack Jill还不成熟,有些功能不支持

android{
  productFlavors{
        google{
            //方法
            useJack true
            //属性
            useJack=true
        }
    }
}

demension

demension是ProductFlavor的一个属性,继承于String,作为该ProductFlavor的维度.

其实可以简单理解为对ProductFlavor进行分组,比如version(free paid),abi(x86 arm).

但使用前需要通过android.flavorDimensions声明

android{
    ...
    flavorDimensions "abi","version"
    productFlavors{
        free{
            dimension 'version'
        }
        paid{
            dimension 'version'
        }
        x86{
            dimension 'abi'
        }
        arm{
            dimension 'abi'
        }       
    }
}

最后生成的variant会被如下的ProductFlavor对象配置.

1.Android里的defaultConfig配置:ProductFlavor

2.abi维度的ProductFlavor,被dimension配置标记为abi的ProductFlavor

3.version维度的ProductFlavor,被dimension配置标记为version的ProductFlavor

维度的优先级:

高优先级非常重要,高优先级的flavor会替换优先的资源 代码 配置等,

先声明的优先级较高(如例子中 abi>version>defaultConfig)

有了维度后,ProductFlavor会被维度细分

variant=BuildType + Abi +Version

(例子会有8种)

Lint支持

Lint是一个命令行工具,位于Android Toll目录下
通过lintOptions{}进行配置

andriod{
  lintOptions{
        abortonError true
        warningAsErrors true
        check 'NewApi'
    }
}

abortOnError

用于配置Lint发现错误时是否退出Gradle构建


absolutePaths

配置错误的属处理是否显示绝对路径


check

即是LintOptions的一个属性,也是一个方法,用于配置哪些项目需要Lint检查

android{
    lintOptions{
        //${sdk}\tools
        //NewApi是一个issue id,在终端输入lint --list查看所有可用的id 
        //"SuspicousImport":解释....  中SuspicousImport就是id
        check 'NewApi','InlineApi'
    }
}

checkAllWarnings

:boolean,用于配置是否检查所有警告的issue,包括默认被关闭的issue;
false不检查


checkReleaseBuilds

配置是否检查致命的错误的问题,默认true.一旦发现有’fatal’级别的问题,release构建会被终止


disable

用于关闭指定id的lint 检查

android{
    lintOptions{
        disable 'NewApi','InlineApi'
    }
}

enable

:boolean,开启指定id的lint检查


explainIssues

配置Lint检查出的错误报告是否应该包含解释说明,默认true


htmlOutput

android{
  lintOptions{
        htmlOutput new File("${buildDir}/lintReports/lint-results.html")
    }
}

htmlReport

:boolean,配置是否生成html报告


ignoreWarning

:boolean,配置是否关闭警告级别的警告,默认false


lintConfig

:File,指定Lint的配置文件:Xml,可以接受一些默认的配置


noLines

:boolean,配置是否关闭error输出时不包含源代码的行号,默认true


quiet

是否开启安静模式,安静模式时Lint分析的进度或其他信息不会被显示


severityOverrides

一个只读属性,返回一个map类型的结果,用于获取issue的优先级.
key是issue id,value是优先级(“fatal”,”error”,”warning”,”information”,”ignore”)


showAll

:boolean,配置是否显示所有的输出,比且不会对过长的消息截断


textOutput

只读属性,:File,用于指定text格式的报告的路径.
如果指定stdout,会被指向标准的输出,一般是终端控制台


textReport

:boolean,默认false,配置是否生成text报告


warningAsErrors

:boolean,是否将警告视作错误处理,默认false


xmlOutput

:File,设置生成的XML报告的路径


error fatal ignore warning informational

用来配置issue的优先级
如,error()就是把指定issue强制指定为error这个优先级


Android Ndk支持

配置环境

//local.properties
sdk.dir=/home/frame/android/android-sdk
ndk.dir=/home/frame/android/android-ndk
//gradle.properties
android.useDeprecatedNdk=true

编译C/C++代码

1.首先定义一个java类,具有一个Native方法

package org.flysnow.app.example
public class HelloWorld{
  public native String getHelloWorld();
}

2.将class文件生成头文件

//在目录中打开cmd并执行 javah -jni org.flysnow.app.example.HelloWorld
//一般在build/intermediates/classes/debug目录中可以找到h文件
//org_flysnow_app_example_HelloWorld.h复制到main/jni目录下

AndroidGradle权威指南__读书笔记_第4张图片
3.实现头文件定义的方法
在jni下创建一个org_flysnow_app_example_HelloWorld.c文件
并实现Java_org_flysnow_app_example_HelloWorld_getHelloWorld()

//main/jni/org_flysnow_app_example_HelloWorld.c
#include "org_flysnow_app_example_HelloWorld.h"
JNIEXPORT jstring JNICALL Java_org_flysnow_app_example_HelloWorld_getHelloWorld(JNIEnv *env
,jobject obj){
    return (*env)->NewStringUTF(env,"你好世界");
}

4.配置so库的模块名
productFlavor.ndk{
//it-> NdkOption
}

android{
  ...
  defaultConfig{
        ...
        ndk{
            moduleName 'helloworld'
        }
    }
}

5.在代码中使用

public class HelloWorld{
  static {
        System.loadLibrary("helloworld");
    }
    pulic native String getHelloWorld();
}
logger.d(new HelloWorld().getHelloWorld())

注意事项

进行Ndk开发时,级别是不能乱用的,这个级别必须是ndk支持的,也就是说我们开始时的sdk级别,ndk也要存在;
这些AndroidGradle配置都会被转换成Android.mk里的配置,这个文件有AndroidGradle自动生成以供android-ndk使用,一般在build/internaliates/ndk目录下

多平台编译

默认情况下生成的so文件包含4个平台架构:armeabi armeabi-v7a mips x86,
但为了减少apk包大小,我们会设置仅支持指定的平台.

android{
    ...
  defaultConfig{
        ...
        ndk{
            moduleName 'helloworld'
            abiFilters 'armeabi-v7a','x86'
            //or
            abiFilter 'armeabi-v7a'
            abiFilter 'x86'
        }
    }
}

使用第三方的So库

在引用第三方的so库时,只需要把第三方给的so库放到特定的目录即可,
src/main/jniLibs 与 我们的jni目录是平级的
如果使用的so库是指定平台的
,如把x86库放到src/main/jniLibs/x86目录中

使用Ndk的库

Ndk提供了很多好用的so库,如日志库liblog 压缩库libz Android本身应用库libandroid库等等
如需使用需要进行配置

//使用android.ndk.idLibs()进行配置
android{
    ...
    defaultConfig{
        ...
        ndk{
            moduleName 'helloworld'
            //必须是moduleName不能带有lib前缀
            ldLibs 'log','z'
        }
    }
}
//配置后就能在C/C++源文件中使用他们了
#include
#include"org_flysnow_app_example_HelloWorld.h"
JNIEXPORT jstring JNICALL Java_org_flysnow_app_example_HelloWorld_getHelloWorld(JNIEnv *env,jobject obj){
        _android_log_print(ANDROID_LOG_INFO,"TAG","CONTENT")
        return null;
}

Android C++支持

p228

AndroidGradle持续集成

p231

你可能感兴趣的:(进阶)