本文是基于Gradle Android Plugin中文手册总结,以及自己本人测试所写
Gradle的目录结构
Gradle遵循约定优先于配置的概念,在尽可能的情况下提供默认的配置参数。最基本的项目有两个”source set”组件,他们是:
- src/main
- src/androidTest在里面,每个存在的文件夹对应相对应的源组件,比如对应java plugin和Android plugin来说,他们的java代码和资源路径如下:java/
resources/
但是对于Android plugin来说,它还拥有一下特有的文件夹结构:
- AndroidManifest.xml
- res/
- assets/
- aild
- rs
- jni
- jniLibs
这就意味着在Android plugin下,*.java的文件的目录是src/main/java,对于AndroidManifest.xml的目录是src/main/AndroidManifest.xml
配置目录结构
有一些时候我们使用一些别的项目的代码,或者是把一些不属于Android项目的资源之类的引入进来,可以通过如下配置,在build.gradle中引入即可,可以引入的资源和方法:
android{
main{
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resorces.sreDirs = ['src']
aidl.srcDirs=['src']
renderscript.srcDirs=['src]
res.srcDirs=['res']
assets.srcDirs=[assets]
}
androidTest.setRoot('tests')
}
其中,在Android特有的sourceSets在Java sourceSets中不起作用。假如是多个,比如java.srcDirs包含多个,可以使用”,”分割开来,比如 java.srcDirs=[‘src-gen’,’src-test’]。setRoot的作用就是把另外一个可以是作为独立的测试的代码资源的文件夹安装main的格式合并进来,作为某一个builtType特有的资源。可以用作是测试,或者是dev或者是release等,在builtType中再去详细介绍。例子:比如我们现在有一个使用greendao产生的src-gen的java代码,根目录就是src-gen,里面包好了greendao帮助我们产生的关于数据库的代码,我们再不适用copy进入项目而是可以直接使用的方式是:把src-gen目录拷贝到跟build.gradle同级目录,然后在build.gradle中的android节点下面,加入:
sourceSets{
main{
java.srcDirs=['src-gen']
}
}
这样子之后,我们就可以在我们的项目中引入和使用这些代码了。
配通用task
在添加了java或者的Android插件之后,gradle会自动的带有以下task
- assemble 组合项目的所有输出
- check 执行所有的检查任务
- build 执行assemble和check两个task的所有工作
- clean 清空项目的输出,也就是build文件夹会被删除实际上,assemble,Check,build这三个task不做任何事情,他只是一个Task的标志,用来告诉plugin添加实际需要执行的task去完成这些工作。task的依赖关系:当一个task依赖于另外一个task的时候,当task被调用的时候,被依赖的task会先调用,比如check task依赖于一个新task,那么当check运行的时候,新的task会先运行。
获取更改级别的task
gradle tasks查看所有的task列表以及他们之间的依赖关系
gradle tasks --alljava的task
- assemble jar,输出jar
- check test 运行测试
常用的task有assemblebuild
buildDependents buildNeeded classes clean jar testClassesdocumentation
javadochelp
buildEnvironment components dependencies dependencyInsight help model projects properties tasksother
compileJava compileTestJava processResources processTestResourcesverification
check test可以看到,assemble需要jar task,jar需要classes task,classes 是用于编译java生成clazz文件的task。可以使用jar或者是assemble或者是build命令去生成jar。
check依赖于test,task,test task依赖于classes和testClasses,也就是需要运行java获取字节码,同时运行测试android的task
- assemble 输出apk文件
- check 执行所有的检查
- connectedCheck 在一个连接的设备或者是模拟器上面执行检查,他们可以在所有连接的设备上面并行的执行检查
- deviceCheck 通过APIs连接远程设备来执行检查,主要用于CI(持续集成)服务上
- build 执行assemble和check的所有工作
- clean 情况项目的输出这些标志性的的task是必须的,以保证能够在没有设备连接的情况下执行定期检查,注意build task不依赖于device Check
一个Android项目至少拥有两个输出:debug和release,他们有各自的标志构建
assembbleDebug assembleRerlease >
其中,单独的assemble会依赖于上面的两个task,会构建出两个apk文件。注意:Gradle在命令行上支持驼峰命名的task简称,比如
gradle aR等同于
gradle assembleReleaseaD等同于assembleDebug
check task的依赖:check 依赖 lintconnectedCheck 依赖connectedAndroidTestdeviceCheck 进行测试时才会触发。只要可被安装,也就是使用了”com.android.application”gradle插件的项目,就可以被安装。使用
installDebug //安装debug版本 installRelease//安装release版本 uninstallAll //卸载 uninstallDebug //卸载debug版本 uninstallRelease //卸载正式版本 uninstallDebugAndroidTest // 卸载测试Android的常用task有:android task
androidDependencies signingReport sourceSetsbuild task
assemble assembleAndroidTest assembleDebug assembleRelease build buildDependents buildNeeded classes compileDebugAndroidTestSources compileDebugSources compileDebugUnitTestSources compileReleaseSources compileReleaseUnitTestSources compileRetrolambda extractDebugAnnotations extractReleaseAnnotations jar mockableAndroidJar testClassesbuild setup task
init wrapperdocumentation task
javadochelp task
buildEnvironment components dependencies dependencyInsight help model projects properties tasksinstall task
installDebug installDebugAndroidTest uninstallAll uninstallDebug uninstallDebugAndroidTest uninstallReleaseother task,这个比较多,可以在AS的gradle视图查看
assembleDebugAndroidTest assembleDebugUnitTest assembleDefault assembleReleaseUnitTest bundleDebug bundleRelease .....verification task
check connectedAndroidTest connectedCheck connectedDebugAndroidTest deviceAndroidTest deviceCheck lint lintDebug lintRelease test testDebugUnitTest testReleaseUnitTestDSL定义Manifest属性
minSdkVersion //app运行的最多版本 tagetSdkVersion //使用的sdk版本 versionCode //app版本code versionName //app版本name applicationId //包名 testApplicationId //测试包名 testInstrumentationRunner这些属性都是使用AS的android的gradle插件提供的,需要使用插件的某一个,然后在android节点下面定义。比如
android{ compileSdkVersion 23 buildToolsVersion "23.0.1"' defaultConfig{ versionCode 100 versionName "1.0.0" minSdkVersion 14 targetSdkVersion 23 applicationId "com.test.gradle" } }构建类型
通过android 节点下面的buildType节点可以构建不同的类型的apk。目前只是针对一个产品,针对多个产品的可以使用 productFlavors,这个之后再做解析。使用buildType节点,默认的,AS会为我们添加debug和release这两个构建类型。例如:
buildTypes { release { minifyEnabled true signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug{ minifyEnabled false signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }这两种类型。其中,minifyEnabled 是是否混淆,signingConfig 是签名,proguardFiles是混淆的文件。我们一般在开发的时候需要使用这两个版本的apk,方便我们调试。假如,我们需要其他的包名,或者是签名的,可以继续添加构建类型,比如:
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug{ minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } dev{ minifyEnabled false applicationIdSuffix ".dev" signingConfig signingConfigs.debug } }这样子我们就可以构建了另一个一个dev的类型的apk,他的报名时原来的defaultConfig的applicationId+”.dev”,这是他的心新报名,我们允许的时候,可以在AS的左下角,build Variants选择运行类型,或者是在右边的选择对应的task去build或者是install apk都是可以的,因为定义了对应的buildType之后,gradle会生成对应的task,比如这里的会生成assembleDev,可以使用aD进行快速的构建。使用buildType的另外一个好处就是可以对不同的类型使用不同的代码或者是资源,他同样是支持sourceSets的,可以为他自己设置不同的代码,不同的资源,通过
sourceSets.buildType.setRoot('path')其中path就是对应的目录,比如是dev的就是把dev所在的目录,相当于完整的另一个的目录设置进去,就可以作为devbuild时候的资源了。
注意:- manifest 将被合并到 app 的 manifest
- 代码只是换了一个源文件夹
- 资源将叠加到 main 的资源中,并替换已存在的资源。也就是dev这个builtType会合并main文件夹的代码和资源,合并的方式就是上面的方式。
但是实际上,建议使用的是产品定制化,也就是使用productFlavors去实现,关于productFlavors实现不同的产品定制化,在后续给大家分享。
设置签名是位于android节点下面的signingConfigs节点下面,例如:android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "com.example.user.testproject" minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName "1.0" } signingConfigs { debug { storeFile file("C:\\Users\\liweijie\\Desktop\\liweijie.jks") storePassword "liweijie123" keyAlias "liweijie" keyPassword "liweijie123" } release { storeFile file("C:\\Users\\liweijie\\Desktop\\liweijie.jks") storePassword "liweijie123" keyAlias "liweijie" keyPassword "liweijie123" } dev{ storeFile file("C:\\Users\\liweijie\\Desktop\\liweijie.jks") storePassword "liweijie123" keyAlias "liweijie" keyPassword "liweijie123" } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug{ signingConfig signingConfigs.debug minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } dev{ minifyEnabled false applicationIdSuffix ".dev" signingConfig signingConfigs.dev } } compileOptions { sourceCompatibility JavaVersion. VERSION_1_8 targetCompatibility JavaVersion. VERSION_1_8 } sourceSets.dev.setRoot('src/dev') }
这里设置了debug和release签名都是同一个,你也可以设置为不同的签名。其中:storeFile file() 指定签名的路径,可以使用相对路径,把对应的签名放在项目跟对应的build同级目录下去引用storePassword 签名的密码keyAlias 签名的别名keyPassword 别名对应的密码
我们可以定义多个签名给不同的buildType,不同对应不同的签名,但是建议不需要太多,太多的话运行的时候回懵逼,特别是我们通过判断签名是否是正式的签名来判断代码是否是debug状态的时候。
依赖管理
使用dependencies节点,该节点是跟android节点同级的.1、 引用本地的依赖,比如我们的jar,通过
dependencies{ compile fileTree(dir: 'libs', include: ['*.jar']) }他会把跟src同级的libs目录下面的jar文件作为依赖引入进来,当你存放在其他地方的时候,也可以手动的引入进来,比如我们存放在src/lib目录下面,可以通过
dependencies { compile files('src/lib/test.jar') }这一个方式引入进来。2、 引用远程的依赖我们有时候会使用一些github的开源库,或者是自己公司私有的maven库或者是一些其他公司提供的仓库,使用到他们的jar。当然我们可以把他们下载下来存放到libs目录下面进行依赖,但是这样子不是很方便,因为有时候版本变更的比较频繁的时候,我们就需要经常去下载,使用远程依赖我们就是只是需要升级一下版本号,clean一下项目就ok了。使用方式:
dependencies { compile 'com.android.support:appcompat-v7:23.1.0' compile 'com.android.support:design:23.1.0' compile 'io.reactivex:rxandroid:1.2.1' compile 'io.reactivex:rxjava:1.1.6' }这里我们引入rx的依赖和v7已以及support:design依赖。依赖的引入时跟maven左边的那种groupId:artifactId:version比如上面的io.reactivex就是groupId,rxJava就是artifactId,1.1.6就是version需要使用这些依赖,同时我们需要申明仓库地址,比如常用的maven仓库,jcenter仓库,我们在整个project的根目录(也就是声明gradle插件的地方)的build.gradle中申明。比如:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.1' classpath 'me.tatarka:gradle-retrolambda:3.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }3、引用lib依赖我们开发的时候,常常会考虑模块化,多人开发,功能分类等,常常有lib的抽取,同时这个lib有时候也会作为一个公用库,做成arr给别的项目依赖,当我们需要多个lib分开整个app的模块或者是测试我们做成的aar的lib的时候,我们就需要使用模块依赖。我们新建一个android lib,android插件类型是
apply plugin: 'com.android.library'然后需要使用到的app可运行或者是其他的lib,使用
dependencies { compile project(':lib') }其中lib就是你的lib项目。
lib module
lib是可以输出一个arr别的lib或者是可运行的app module使用。lib使用的android插件是com.android.librarylib包含的内容有源代码,资源文件,清单文件,lib本身的相关依赖(so文件或者是其他的arr,jar文件)。他被依赖到别的项目的时候,资源文件和清单会被合并,当有重复命名的时候,比如同样有两个string的name为app_name的,那么lib的将会被覆盖。java代码就是不会被合并,当存在两个包名相同,类名相同的java文件的时候回报多个字节码文件的错误。当一个lib或者是app module依赖多个子lib的时候,假如子lib之间存在着资源冲突,也会发生某某资源已经定义的错误。我们发布被依赖的lib版本的时候,可以通过buildType或者productFlavors去实现,同样可以通过
android { defaultPublishConfig "flavor" }其中,flavor在没有其他定义的产品渠道的时候,是debug或者是release,一般我们使用的是release版本,假如我们需要发布多个arr,包括debug,release或者是其他flavor或者是buildType的时候,可以通过
android { publishNonDefault true }每一个arr都是独立的,包含自己的资源和代码,相对应的sourceSets等。
Gradle构建变种版本
介绍
构建变种版本是有利于我们调试,多渠道打包。比如,我们需要上360,应用宝市场,然后需要区分这两个这两个市场的下载量,卸载量等,我们通过在清档文件带有一个渠道标识,当然,我们可以一次一次的分开打包,没打完一个包然后就再次修改渠道标识再次打包。但是这样子很繁琐时间也很长,我们就可以使用AS使用的Gradle实现变种apk来一次性打完所有的apk。同时使用多渠道打包,我们可以同时打出不同的安装名字和桌面名字的apk。比如在应用宝上面,安装名字是A,桌面名字是B,在360渠道上面安装名字是C,桌面名字是D,使用多渠道就可以一次性打出这些包。
使用
变种打包其实是等于productFlavors+不同的BuildType。上一篇文件我们介绍了BuildType,虽然我们可以使用多个BuildType来达到使用不同的资源,代码,清单,依赖等,但是,当我们需要一次性输出多个apk的时候是行不通的,我们就可以使用productFlavors。首先,我们需要在src目录下面建立一些我们需要的文件夹,就是我们需要打包的那些渠道,比如qihu360,yingyongbao,如下图:
然后,在可运行的app module根目录的build.gradle中的android节点下面,添加
productFlavors{ dev {} dev2 {} qihu360{} yingyongbao{} }这样子,同步一下,然后我们在运行测试的时候,可以在AS左下角BuildVariants中,选择安装不同的变种apk,可以用来测试。他可以运行的种类是productFlavors的数目跟BuildType的数目的排列组合。比如4个变种,2个buildType,就可以输出8个apk。我们通过AS的build工具,进行签名相关,就可以输出多个apk了。如下图:


我们输出的时候一般是选择release版本的需要的变种版本的apk,然后就静静等待一下apk打包完成即可。高级
Android打包的时候回合并资源,上级会覆盖替换下级的资源。所以,假如我们需要统计某一个渠道,可以通过java的多台,使用继承,在各个变种使用不同的Application去继承main中的Application,或者是通过覆盖资源,或者是通过在各个变种的清单文件中添加不同的渠道,这样子就能起到不同的apk有不同的渠道而且可以一次性打包完成的作用。清单文件他也是会合并的。在变种文件夹中,比如上面的qihu360,我们可以建立跟main文件夹一样的目录结构,只不过这些资源或是代码只可以在变种中存在和使用。这里就不一一截图了。使用不同的资源和代码,通过覆盖和替换,我们可以定制各个不同的apk,公用大部分的代码和资源。比如我们的某一些变种需要广告,一些不需要,我们就可以通过java的多台实现。通过在main或者是lib中提供一个接口或者是类,然后在变种中实现,通过在变种的Application类写入,在main或者是lib中通过getApplication()获取,这样子就可以做到不同的变种使用不同的东西。productFlavors中提供的变种除了可以输出不同的apk之外,他还可以设置更多的东西。他可以定制变种的包名,变种的签名,变种的sdk版本等,比如:
productFlavors{ dev { applicationId "com.example.user..dev" minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" signingConfig signingConfigs.dev } dev2 {} qihu360{} yingyongbao{} }因为productFlavors和defaultConfig是同级的,所以defaultConfig的属性在productFlavors中也可以使用。这样子我们也就可以输出更加具有定制化的apk了,可以是不同签名的,不同version和针对不同android 版本的apk。注意一点:applicationId和package的区别:applicationId是在build.gradle中定义的,package是在清单文件中定义的,他们二者可以不同。
applicationId是应用的真正包名,package是应用的资源包名,也就是R文件生成的路径,尹永峰资源的路径。某一些时候,他们不同是会出现一些问题的,比如,在个人实践中发现,使用友盟push的时候,假如不同会获取不到token,就是当你没有显示的设置包名参数给友盟的时候。有时候测试某一些广告的时候,有一些广告也是无法出现的。所以,还是建议这二者一般保持一致避免导致有时候找bug不知道是那里出问题了。