前置文章 :Gradle学习总结
通过下面几个案例来演示下Gradle的强大之处,因为在一般的开发中我们只是简单的配置下build.gradle文件(添加依赖),并不能体现它的强大,也不知道它还有那些用途。
目录
案例一:单独定义一个Gradle文件,存放应用中的所有配置变量和依赖,以便统一管理
common.gradle
//用来存放应用中的所有配置变量,统一管理,而不再是每个moudle里都自己写一份,修改起来更加的方便
ext {//扩展Project的变量
android = [compileSdkVersion : 25,
buildToolsVersion : '25.0.0',
applicationId : 'com.gradle.demo',
minSdkVersion : 16,
targetSdkVersion : 23,
versionCode : 1,
versionName : '1.0.0',
multiDexEnabled : true,
manifestPlaceholders: [UMENG_CHANNEL_VALUE: 'study']]
signConfigs = ['storeFile' : 'sign.jks',
'storePassword': '123456',
'keyAlias' : 'alias',
'keyPassword' : '123456']
java = ['javaVersion': JavaVersion.VERSION_1_7]
dependence = ['libSupportV7' : 'com.android.support:appcompat-v7:25.0.0',
'libSupportMultidex' : 'com.android.support:multidex:1.0.1',
'libPullAlive' : ':lib_pullalive',
'libCircleImageView' : 'de.hdodenhof:circleimageview:2.1.0',
'libSystembarTint' : 'com.readystatesoftware.systembartint:systembartint:1.0.3',
'libUmengAnalytics' : 'com.umeng.analytics:analytics:latest.integration',
'libUniversalImageLoader': 'com.nostra13.universalimageloader:universal-image-loader:1.9.5',
'libOkhttp' : 'com.squareup.okhttp3:okhttp:3.3.0',
'libAutoScrollViewPager' : 'cn.trinea.android.view.autoscrollviewpager:android-auto-scroll-view-pager:1.1.2',
'libSlidableActivity' : 'com.r0adkll:slidableactivity:2.0.5',
'libAndfix' : 'com.alipay.euler:andfix:0.5.0@aar',
'libLogger' : 'com.orhanobut:logger:+',
'libTinker' : "com.tencent.tinker:tinker-android-lib:1.7.7",
'libTinkerAndroid' : "com.tencent.tinker:tinker-android-anno:1.7.7"]
}
定义好文件后我们在根工程中引入该文件
//引入文件
apply from: this.file('common.gradle')
buildscript {
//配置工程的仓库地址
repositories {
jcenter()
}
//配置工程的"插件"依赖地址
dependencies {
classpath "com.android.tools.build:gradle:2.2.2"
}
}
引入文件后,我们在app工程build.gradle文件中使用
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
applicationId rootProject.ext.android.applicationId
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
multiDexEnabled rootProject.ext.android.multiDexEnabled //突破应用方法数65535的一个限制
}
signingConfigs {
//签名打包
release {
storeFile file(rootProject.ext.signConfigs.storeFile)
storePassword rootProject.ext.signConfigs.storePassword
keyAlias rootProject.ext.signConfigs.keyAlias
keyPassword rootProject.ext.signConfigs.keyPassword
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
lintOptions {
abortOnError false
lintConfig file("lint.xml")
}
//recommend
dexOptions {
jumboMode = true
}
compileOptions {
sourceCompatibility rootProject.ext.java.javaVersion
targetCompatibility rootProject.ext.java.javaVersion
}
sourceSets {
main {
jniLibs.srcDirs = ['libs'] //修改so库存放位置
}
}
}
//为应用程序添加第三方库依赖
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile rootProject.ext.dependence.libSupportV7
compile rootProject.ext.dependence.libSupportMultidex
compile project(rootProject.ext.dependence.libPullAlive)
compile rootProject.ext.dependence.libCircleImageView
compile rootProject.ext.dependence.libSystembarTint
//添加友盟统计
compile rootProject.ext.dependence.libUmengAnalytics
compile rootProject.ext.dependence.libUniversalImageLoader
compile rootProject.ext.dependence.libOkhttp
//okttp依赖
//compile 'com.github.chrisbanes:PhotoView:1.3.0'
compile(rootProject.ext.dependence.libAutoScrollViewPager) {
exclude module: 'support-v4' //排除依赖
exclude group: 'com.android.support' //排除该组下所有的依赖
transitive false //禁止传递依赖
}
compile rootProject.ext.dependence.libSlidableActivity
//滑动关闭Activity库
compile rootProject.ext.dependence.libAndfix
//阿里热修复andfix
compile rootProject.ext.dependence.libLogger
//日志库logger
//Tinker相关依赖
compile(rootProject.ext.dependence.libTinker) {
changing = true //每次都从服务端拉取
}
provided(rootProject.ext.dependence.libTinkerAndroid) { changing = true }
}
这是配置后,我们的app工程的build.gradle文件更有语义性,版本变化后也便于管理,同时还可以复用。
案例二:自动维护版本发布文档
我们APP每次迭代需要记录APP的版本信息和对应的变化信息,以某一特定的格式记录,方便以后对比管理。下面先看一下版本发布文档的样式:(当然你也可以定义自己想要的样式)
<releases>
<release>
<versionCode>100</versionCode>
<versionName>1.0.0</versionName>
<versionInfo>App的第1个版本,上线了一些最基础核心的功能.</versionInfo>
</release>
<release>
<versionCode>101</versionCode>
<versionName>1.0.1</versionName>
<versionInfo>App的第2个版本,修复了XXXXXBug,优化XXXX功能</versionInfo>
</release>
.................. 省略 ...................
<release>
<versionCode>110</versionCode>
<versionName>1.1.0</versionName>
<versionInfo>第10个版本。。。</versionInfo>
</release>
</releases>
有了这样的文档我们可以很方便的对比每个版本的差异,我们可以写一个脚本来自动生成这样的文档,而不需要手动维护。
releaseinfo.gradle
import groovy.xml.MarkupBuilder
/**
* 描述:版本发布文档自动维护脚本
* 流程描述:
* 1、将版本相关信息解析出来
* 2、将解析出的数据生成xml格式数据
* 3、写入到已有的文档数据中
**/
ext {
versionName = rootProject.ext.android.versionName
versionCode = rootProject.ext.android.versionCode
versionInfo = 'App的第2个版本,上线了一些最基础核心的功能.'
destFile = file('releases.xml')//指定输出文件
if (destFile != null && !destFile.exists()) {
destFile.createNewFile()
}
}
//创建一个Task,并指定输入输出
task writeTask {
inputs.property('versionCode', this.versionCode)
inputs.property('versionName', this.versionName)
inputs.property('versionInfo', this.versionInfo)
outputs.file this.destFile
doLast {
//将输入的内容写入到输出文件中去
def data = inputs.getProperties()
File file = outputs.getFiles().getSingleFile()
def versionMsg = new VersionMsg(data)
//将实体对象写入到xml文件中
def sw = new StringWriter()
def xmlBuilder = new MarkupBuilder(sw)
if (file.text != null && file.text.size() <= 0) {
//没有内容
xmlBuilder.releases {
release {
versionCode(versionMsg.versionCode)
versionName(versionMsg.versionName)
versionInfo(versionMsg.versionInfo)
}
}
//直接写入
file.withWriter { writer -> writer.append(sw.toString())
}
} else {
//已有其它版本内容
xmlBuilder.release {
versionCode(versionMsg.versionCode)
versionName(versionMsg.versionName)
versionInfo(versionMsg.versionInfo)
}
//插入到最后一行前面
def lines = file.readLines()
def lengths = lines.size() - 1
file.withWriter { writer ->
lines.eachWithIndex { line, index ->
if (index != lengths) {
writer.append(line + '\r\n')
} else if (index == lengths) {
writer.append('\r\r\n' + sw.toString() + '\r\n')
writer.append(lines.get(lengths))
}
}
}
}
}
}
//信息实体类
class VersionMsg {
String versionCode
String versionName
String versionInfo
}
//通过输入输出来指定Task的执行顺序
task readTask {
//指定输入文件为上一个task的输出
inputs.file this.destFile
doLast {
//读取输入文件的内容并显示
def file = inputs.files.singleFile
println file.text
}
}
task taskZ {
dependsOn writeTask, readTask//通过依赖指定Task的执行顺序
doLast {
println '输入输出任务结束'
}
}
//将文档复制到指定的文件中
task handleReleaseFile {
def srcFile = file('releases.xml')
def destDir = new File(this.buildDir, 'generated/release/')
doLast {
println '开始解析对应的xml文件...'
destDir.mkdir()
def releases = new XmlParser().parse(srcFile)
releases.release.each { releaseNode ->
//解析每个release结点的内容
def name = releaseNode.versionName.text()
def versionCode = releaseNode.versionCode.text()
def versionInfo = releaseNode.versionInfo.text()
//创建文件并写入结点数据
def destFile = new File(destDir, "release-${name}.txt")
destFile.withWriter { writer -> writer.write("${name} -> ${versionCode} -> ${versionInfo}")
}
}
}
}
当版本改变的时候,我们只需要修改versionInfo 对应的内容,然后执行Task就可以生成对应的文档了,不过上述脚本还是需要我们手动修改versionInfo 的内容,我们可以把versionInfo 的内容从服务器获取(获取版本信息),这样的话就不需要我们手动修改了,groovy对网络访问这块没有特殊的扩展,所以我们用java的请求网络的方式实现即可。
案例三:项目发送到远程maven库
//是否需要把项目发送到远程maven库
ext.publishToRemote = true
ext.publishDefaultArtifact = !"true".equals(project.getProperties().get("org.gradle.parallel"))
ext.publishApk = false
ext.isApplication = false
if (!project.getBuildFile().exists()) {
return;
}
apply plugin: 'maven'
apply plugin: 'maven-publish'
configurations {
providedCompile
compile.extendsFrom providedCompile
}
repositories {
// mavenLocal() :maven本地仓库
maven {
url "maven仓库地址"
}
}
if (!project.getRootProject().hasProperty("aarMap")) {
project.getRootProject().ext.set("aarMap", new HashSet<String>())
}
def deployVersion = System.getProperty('deployVersion')
//配置完成之后执行
project.afterEvaluate {
if (project.plugins.hasPlugin("com.android.library")) {
project.getRootProject().aarMap.add(project.name)
}
ext.isApplication = (project.plugins.hasPlugin("com.android.application"))
tasks.whenTaskAdded { task ->
if (task.name.startsWith("generatePomFileForMavenPublication")) {
task.doFirst {
project.publishing.publications.maven(MavenPublication) {
if (!components.hasWithName("java") && !isApplication) {
File f = file("${project.buildDir}/outputs/awb/${project.name}-release.awb");
if (!f.exists()) {
f = file("${project.buildDir}/outputs/aar/${project.name}-release.aar");
}
if (!f.exists()) {
f = file("${project.buildDir}/outputs/awb/${project.name}-debug.awb");
}
if (!f.exists()) {
f = file("${project.buildDir}/outputs/aar/${project.name}-debug.aar");
}
artifact f.getPath()
}
}
}
}
if (isApplication && !publishApk) {
if (task.name.startsWith("publish")) {
task.setEnabled(false)
}
}
}
}
def HashMap getAccount() {
HashMap accountMap = new HashMap()
def parsedSettingsXml
def settingsFile = '/Users/root/Downloads/android/apache-maven-3.3.9/conf/settings.xml'
def defaultSettingsFile = System.getProperty("user.home") + "/.m2/settings.xml"
if (file(settingsFile).exists() || file(defaultSettingsFile).exists()) {
if (file(settingsFile).exists()) {
parsedSettingsXml = (new XmlParser()).parse(settingsFile);
} else if (file(defaultSettingsFile).exists()) {
parsedSettingsXml = (new XmlParser()).parse(defaultSettingsFile);
}
parsedSettingsXml.servers[0].server.each { server ->
if ("releases" == server.id.text()) {
accountMap.put("id", server.id.text())
accountMap.put("username", server.username.text())
accountMap.put("password", server.password.text())
}
}
} else {
accountMap.put("id", "releases")
accountMap.put("username", "admin")
accountMap.put("password", "admin123")
}
return accountMap
}
publishing {
if (null != deployVersion) {
version = deployVersion
}
publications {
maven(MavenPublication) {
version version
task sourceJar(type: Jar) {
classifier = 'source'
version = version
try {
if (components.hasWithName("java")) {
from sourceSets.main.allJava
} else {
from android.sourceSets.main.java.srcDirs
}
} catch (Throwable e) {
}
}
artifact sourceJar
if (components.hasWithName("java") || components.hasWithName("android")) {
if (components.hasWithName("java")) {
from components.java
} else if (!isApplication) {
from components.android
}
pom.withXml {
asNode().dependencies.'*'.each {
if (it.scope.text() == 'runtime') {
if (project.configurations.providedCompile.allDependencies.find { dep -> dep.name == it.artifactId.text() }) {
it.scope*.value = 'provided'
} else if (project.configurations.compile.allDependencies.find { dep -> dep.name == it.artifactId.text() }) {
it.scope*.value = 'compile'
}
}
}
}
pom.withXml {
asNode().dependencies.'*'.findAll() {
it.groupId.text() == groupId && project.getRootProject().aarMap.contains(it.artifactId.text())
}.each { it.appendNode('type', 'aar') }
}
if (!components.hasWithName("java") && !isApplication) {
pom.withXml {
def dependenciesNode = asNode().dependencies[0]
if (getGradle().startParameter.toString().contains("assembleDebug")) {
configurations.debugCompile.allDependencies.each {
if (it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
if (it.group == groupId && project.getRootProject().aarMap.contains(it.name)) {
dependencyNode.appendNode('type', 'aar')
}
}
}
} else {
configurations.releaseCompile.allDependencies.each {
if (it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
if (it.group == groupId && project.getRootProject().aarMap.contains(it.name)) {
dependencyNode.appendNode('type', 'aar')
}
}
}
}
}
}
} else if (!isApplication) {
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
def providedCompiles = new HashSet();
configurations.providedCompile.allDependencies.each {
if (it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
dependencyNode.appendNode('scope', 'provided')
if (it.group == groupId && project.getRootProject().aarMap.contains(it.name)) {
dependencyNode.appendNode('type', 'aar')
}
providedCompiles.add(it.group + "." + it.name)
}
}
configurations.compile.allDependencies.each {
if (it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) {
if (!providedCompiles.contains(it.group + "." + it.name)) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
if (it.group == groupId && project.getRootProject().aarMap.contains(it.name)) {
dependencyNode.appendNode('type', 'aar')
}
}
}
}
if (getGradle().startParameter.toString().contains("assembleDebug")) {
configurations.debugCompile.allDependencies.each {
if (it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) {
if (!providedCompiles.contains(it.group + "." + it.name)) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
if (it.group == groupId && project.getRootProject().aarMap.contains(it.name)) {
dependencyNode.appendNode('type', 'aar')
}
}
}
}
} else {
configurations.releaseCompile.allDependencies.each {
if (it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null) {
if (!providedCompiles.contains(it.group + "." + it.name)) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
if (it.group == groupId && project.getRootProject().aarMap.contains(it.name)) {
dependencyNode.appendNode('type', 'aar')
}
}
}
}
}
}
}
}
}
repositories {
mavenLocal()
if (publishToRemote) {
if (version.endsWith("-SNAPSHOT")) {
maven {
url "maven仓库远程地址"
credentials {
username = "admin"
password = "admin123"
}
}
} else {
def accountMap = getAccount();
maven {
url "maven仓库本地地址"
credentials {
username = accountMap.get("username")
password = accountMap.get("password")
}
}
}
}
}
}
案例四:多渠道打包
渠道包就是要在安装包中添加渠道信息,也就是channel,对应不同的渠道,例如:小米市场、360市场、应用宝市场等 。我们要在安装包中添加不同的标识,应用在请求网络的时候携带渠道信息,方便后台做运营统计(这就是添加渠道信息的用处)。
在AndroidMainfest.xml配置相应的渠道
<meta-data android:value="UMENG_CHANNEL"
android:name="${UMENG_CHANNEL_VALUE}"/> <!--动态更改渠道号-->
在build.gradle中配置渠道信息和自动替换脚本
//多渠道打包
productFlavors {
xiaomi {}
huawei {}
yingyongbao {}
wandoujia {}
}
//自动替换清单文件中的渠道号
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
默认配置
defaultConfig {
applicationId "com.gradle.demo"
minSdkVersion 11
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true //突破应用方法数65535的限制
}
所有渠道默认使用这一配置,如果渠道有特殊需求,可以在productFlavors对应的渠道号中单独配置。
打包后自动修改APK的名字
//release包的命名格式为:产品名_版本号_渠道名.apk
//debug包的命名格式为:产品名_版本号-debug.apk
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (null != outputFile && outputFile.name.endsWith('.apk')) {
File outputDir = new File(outputFile.parent);
def baseName = PRODUCT_NAME + "${defaultConfig.versionName}" + "_" + variant.productFlavors[0].name
def newApkName
if (variant.buildType.name.equals('release')) {
newApkName = baseName + '.apk'
} else if (variant.buildType.name.equals('debug')) {
def debugName = PRODUCT_NAME + "${defaultConfig.versionName}"
newApkName = debugName + "-debug.apk"
}
output.outputFile = new File(outputDir, newApkName)
}
}
}
完整build.gradle文件内容如下:
apply plugin: 'com.android.application'
//产品名
def PRODUCT_NAME = "BuglyDemo"
android {
//添加签名文件配置
signingConfigs {
mysigns {
keyAlias 'alias'
keyPassword '123456'
storeFile file('sign.jks')
storePassword '123456'
}
}
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.gradle.demo"
minSdkVersion 11
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true //突破应用方法数65535的限制
}
//多渠道打包
productFlavors {
xiaomi {}
huawei {}
yingyongbao {}
wandoujia {}
}
//自动替换清单文件中的渠道号
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
buildTypes {
release {
minifyEnabled false //是否启用混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debuggable false
jniDebuggable false
signingConfig signingConfigs.mysigns
renderscriptDebuggable false
minifyEnabled false
pseudoLocalesEnabled false
zipAlignEnabled true
}
}
//release包的命名格式为:产品名版本号渠道名.apk
//debug包的命名格式为:产品名_版本号-debug.apk
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (null != outputFile && outputFile.name.endsWith('.apk')) {
File outputDir = new File(outputFile.parent);
def baseName = PRODUCT_NAME + "${defaultConfig.versionName}" + "_" + variant.productFlavors[0].name
def newApkName
if (variant.buildType.name.equals('release')) {
newApkName = baseName + '.apk'
} else if (variant.buildType.name.equals('debug')) {
def debugName = PRODUCT_NAME + "${defaultConfig.versionName}"
newApkName = debugName + "-debug.apk"
}
output.outputFile = new File(outputDir, newApkName)
}
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.1'
testCompile 'junit:junit:4.12'
//添加友盟统计库依赖
compile 'com.umeng.analytics:analytics:latest.integration'
}