Gradle实战
Posted 安卓行动派
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gradle实战相关的知识,希望对你有一定的参考价值。
通过下面几个案例来演示下Gradle的强大之处,因为在一般的开发中我们只是简单的配置下build.gradle文件(添加依赖),并不能体现它的强大,也不知道它还有那些用途。
目录
案例一:单独定义一个Gradle文件,存放应用中的所有配置变量和依赖,以便统一管理
common.gradle
1//用来存放应用中的所有配置变量,统一管理,而不再是每个moudle里都自己写一份,修改起来更加的方便
2ext {//扩展Project的变量
3
4 android = [compileSdkVersion : 25,
5 buildToolsVersion : '25.0.0',
6 applicationId : 'com.gradle.demo',
7 minSdkVersion : 16,
8 targetSdkVersion : 23,
9 versionCode : 1,
10 versionName : '1.0.0',
11 multiDexEnabled : true,
12 manifestPlaceholders: [UMENG_CHANNEL_VALUE: 'study']]
13
14 signConfigs = ['storeFile' : 'sign.jks',
15 'storePassword': '123456',
16 'keyAlias' : 'alias',
17 'keyPassword' : '123456']
18
19 java = ['javaVersion': JavaVersion.VERSION_1_7]
20
21
22 dependence = ['libSupportV7' : 'com.android.support:appcompat-v7:25.0.0',
23 'libSupportMultidex' : 'com.android.support:multidex:1.0.1',
24 'libPullAlive' : ':lib_pullalive',
25 'libCircleImageView' : 'de.hdodenhof:circleimageview:2.1.0',
26 'libSystembarTint' : 'com.readystatesoftware.systembartint:systembartint:1.0.3',
27 'libUmengAnalytics' : 'com.umeng.analytics:analytics:latest.integration',
28 'libUniversalImageLoader': 'com.nostra13.universalimageloader:universal-image-loader:1.9.5',
29 'libOkhttp' : 'com.squareup.okhttp3:okhttp:3.3.0',
30 'libAutoScrollViewPager' : 'cn.trinea.android.view.autoscrollviewpager:android-auto-scroll-view-pager:1.1.2',
31 'libSlidableActivity' : 'com.r0adkll:slidableactivity:2.0.5',
32 'libAndfix' : 'com.alipay.euler:andfix:0.5.0@aar',
33 'libLogger' : 'com.orhanobut:logger:+',
34 'libTinker' : "com.tencent.tinker:tinker-android-lib:1.7.7",
35 'libTinkerAndroid' : "com.tencent.tinker:tinker-android-anno:1.7.7"]
36}
定义好文件后我们在根工程中引入该文件
1//引入文件
2apply from: this.file('common.gradle')
3buildscript {
4 //配置工程的仓库地址
5 repositories {
6 jcenter()
7 }
8 //配置工程的"插件"依赖地址
9 dependencies {
10 classpath "com.android.tools.build:gradle:2.2.2"
11 }
12}
引入文件后,我们在app工程build.gradle文件中使用
1apply plugin: 'com.android.application'
2android {
3 compileSdkVersion rootProject.ext.android.compileSdkVersion
4 buildToolsVersion rootProject.ext.android.buildToolsVersion
5
6 defaultConfig {
7 applicationId rootProject.ext.android.applicationId
8 minSdkVersion rootProject.ext.android.minSdkVersion
9 targetSdkVersion rootProject.ext.android.targetSdkVersion
10 versionCode rootProject.ext.android.versionCode
11 versionName rootProject.ext.android.versionName
12 multiDexEnabled rootProject.ext.android.multiDexEnabled //突破应用方法数65535的一个限制
13 }
14
15 signingConfigs {
16 //签名打包
17 release {
18 storeFile file(rootProject.ext.signConfigs.storeFile)
19 storePassword rootProject.ext.signConfigs.storePassword
20 keyAlias rootProject.ext.signConfigs.keyAlias
21 keyPassword rootProject.ext.signConfigs.keyPassword
22 }
23 }
24
25 buildTypes {
26 release {
27 minifyEnabled true
28 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29 signingConfig signingConfigs.release
30 }
31 }
32
33 lintOptions {
34 abortOnError false
35 lintConfig file("lint.xml")
36 }
37
38 //recommend
39 dexOptions {
40 jumboMode = true
41 }
42
43 compileOptions {
44 sourceCompatibility rootProject.ext.java.javaVersion
45 targetCompatibility rootProject.ext.java.javaVersion
46 }
47
48 sourceSets {
49 main {
50 jniLibs.srcDirs = ['libs'] //修改so库存放位置
51 }
52 }
53}
54
55//为应用程序添加第三方库依赖
56dependencies {
57 compile fileTree(include: ['*.jar'], dir: 'libs')
58 compile rootProject.ext.dependence.libSupportV7
59 compile rootProject.ext.dependence.libSupportMultidex
60 compile project(rootProject.ext.dependence.libPullAlive)
61 compile rootProject.ext.dependence.libCircleImageView
62 compile rootProject.ext.dependence.libSystembarTint
63 //添加友盟统计
64 compile rootProject.ext.dependence.libUmengAnalytics
65 compile rootProject.ext.dependence.libUniversalImageLoader
66 compile rootProject.ext.dependence.libOkhttp
67 //okttp依赖
68 //compile 'com.github.chrisbanes:PhotoView:1.3.0'
69 compile(rootProject.ext.dependence.libAutoScrollViewPager) {
70 exclude module: 'support-v4' //排除依赖
71 exclude group: 'com.android.support' //排除该组下所有的依赖
72 transitive false //禁止传递依赖
73 }
74 compile rootProject.ext.dependence.libSlidableActivity
75 //滑动关闭Activity库
76 compile rootProject.ext.dependence.libAndfix
77 //阿里热修复andfix
78 compile rootProject.ext.dependence.libLogger
79 //日志库logger
80 //Tinker相关依赖
81 compile(rootProject.ext.dependence.libTinker) {
82 changing = true //每次都从服务端拉取
83 }
84 provided(rootProject.ext.dependence.libTinkerAndroid) { changing = true }
85}
这是配置后,我们的app工程的build.gradle文件更有语义性,版本变化后也便于管理,同时还可以复用。
案例二:自动维护版本发布文档
我们APP每次迭代需要记录APP的版本信息和对应的变化信息,以某一特定的格式记录,方便以后对比管理。下面先看一下版本发布文档的样式:(当然你也可以定义自己想要的样式)
1<releases>
2 <release>
3 <versionCode>100</versionCode>
4 <versionName>1.0.0</versionName>
5 <versionInfo>App的第1个版本,上线了一些最基础核心的功能.</versionInfo>
6 </release>
7
8<release>
9 <versionCode>101</versionCode>
10 <versionName>1.0.1</versionName>
11 <versionInfo>App的第2个版本,修复了XXXXXBug,优化XXXX功能</versionInfo>
12</release>
13 .................. 省略 ...................
14<release>
15 <versionCode>110</versionCode>
16 <versionName>1.1.0</versionName>
17 <versionInfo>第10个版本。。。</versionInfo>
18</release>
19</releases>
有了这样的文档我们可以很方便的对比每个版本的差异,我们可以写一个脚本来自动生成这样的文档,而不需要手动维护。
releaseinfo.gradle
1import groovy.xml.MarkupBuilder
2
3/**
4 * 描述:版本发布文档自动维护脚本
5 * 流程描述:
6 * 1、将版本相关信息解析出来
7 * 2、将解析出的数据生成xml格式数据
8 * 3、写入到已有的文档数据中
9 **/
10ext {
11 versionName = rootProject.ext.android.versionName
12 versionCode = rootProject.ext.android.versionCode
13 versionInfo = 'App的第2个版本,上线了一些最基础核心的功能.'
14 destFile = file('releases.xml')//指定输出文件
15 if (destFile != null && !destFile.exists()) {
16 destFile.createNewFile()
17 }
18}
19//创建一个Task,并指定输入输出
20task writeTask {
21 inputs.property('versionCode', this.versionCode)
22 inputs.property('versionName', this.versionName)
23 inputs.property('versionInfo', this.versionInfo)
24 outputs.file this.destFile
25 doLast {
26 //将输入的内容写入到输出文件中去
27 def data = inputs.getProperties()
28 File file = outputs.getFiles().getSingleFile()
29 def versionMsg = new VersionMsg(data)
30 //将实体对象写入到xml文件中
31 def sw = new StringWriter()
32 def xmlBuilder = new MarkupBuilder(sw)
33 if (file.text != null && file.text.size() <= 0) {
34 //没有内容
35 xmlBuilder.releases {
36 release {
37 versionCode(versionMsg.versionCode)
38 versionName(versionMsg.versionName)
39 versionInfo(versionMsg.versionInfo)
40 }
41 }
42 //直接写入
43 file.withWriter { writer -> writer.append(sw.toString())
44 }
45 } else {
46 //已有其它版本内容
47 xmlBuilder.release {
48 versionCode(versionMsg.versionCode)
49 versionName(versionMsg.versionName)
50 versionInfo(versionMsg.versionInfo)
51 }
52 //插入到最后一行前面
53 def lines = file.readLines()
54 def lengths = lines.size() - 1
55 file.withWriter { writer ->
56 lines.eachWithIndex { line, index ->
57 if (index != lengths) {
58 writer.append(line + '\r\n')
59 } else if (index == lengths) {
60 writer.append('\r\r\n' + sw.toString() + '\r\n')
61 writer.append(lines.get(lengths))
62 }
63 }
64 }
65 }
66 }
67}
68//信息实体类
69class VersionMsg {
70 String versionCode
71 String versionName
72 String versionInfo
73}
74//通过输入输出来指定Task的执行顺序
75task readTask {
76 //指定输入文件为上一个task的输出
77 inputs.file this.destFile
78 doLast {
79 //读取输入文件的内容并显示
80 def file = inputs.files.singleFile
81 println file.text
82 }
83}
84
85task taskZ {
86 dependsOn writeTask, readTask//通过依赖指定Task的执行顺序
87 doLast {
88 println '输入输出任务结束'
89 }
90}
91
92//将文档复制到指定的文件中
93task handleReleaseFile {
94 def srcFile = file('releases.xml')
95 def destDir = new File(this.buildDir, 'generated/release/')
96 doLast {
97 println '开始解析对应的xml文件...'
98 destDir.mkdir()
99 def releases = new XmlParser().parse(srcFile)
100 releases.release.each { releaseNode ->
101 //解析每个release结点的内容
102 def name = releaseNode.versionName.text()
103 def versionCode = releaseNode.versionCode.text()
104 def versionInfo = releaseNode.versionInfo.text()
105 //创建文件并写入结点数据
106 def destFile = new File(destDir, "release-${name}.txt")
107 destFile.withWriter { writer -> writer.write("${name} -> ${versionCode} -> ${versionInfo}")
108 }
109 }
110 }
111
112}
当版本改变的时候,我们只需要修改versionInfo 对应的内容,然后执行Task就可以生成对应的文档了,不过上述脚本还是需要我们手动修改versionInfo 的内容,我们可以把versionInfo 的内容从服务器获取(获取版本信息),这样的话就不需要我们手动修改了,groovy对网络访问这块没有特殊的扩展,所以我们用java的请求网络的方式实现即可。
案例三:多渠道打包
渠道包就是要在安装包中添加渠道信息,也就是channel,对应不同的渠道,例如:小米市场、360市场、应用宝市场等 。我们要在安装包中添加不同的标识,应用在请求网络的时候携带渠道信息,方便后台做运营统计(这就是添加渠道信息的用处)。
在AndroidMainfest.xml配置相应的渠道
1<meta-data android:value="UMENG_CHANNEL"
2 android:name="${UMENG_CHANNEL_VALUE}"/> <!--动态更改渠道号-->
在build.gradle中配置渠道信息和自动替换脚本
1//多渠道打包
2productFlavors {
3 xiaomi {}
4 huawei {}
5 yingyongbao {}
6 wandoujia {}
7}
8
9//自动替换清单文件中的渠道号
10productFlavors.all {
11 flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
12}
默认配置
1defaultConfig {
2 applicationId "com.gradle.demo"
3 minSdkVersion 11
4 targetSdkVersion 25
5 versionCode 1
6 versionName "1.0"
7 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
8 multiDexEnabled true //突破应用方法数65535的限制
9}
所有渠道默认使用这一配置,如果渠道有特殊需求,可以在productFlavors对应的渠道号中单独配置。
打包后自动修改APK的名字
1//release包的命名格式为:产品名_版本号_渠道名.apk
2//debug包的命名格式为:产品名_版本号-debug.apk
3applicationVariants.all { variant ->
4 variant.outputs.each { output ->
5 def outputFile = output.outputFile
6 if (null != outputFile && outputFile.name.endsWith('.apk')) {
7 File outputDir = new File(outputFile.parent);
8 def baseName = PRODUCT_NAME + "${defaultConfig.versionName}" + "_" + variant.productFlavors[0].name
9 def newApkName
10 if (variant.buildType.name.equals('release')) {
11 newApkName = baseName + '.apk'
12 } else if (variant.buildType.name.equals('debug')) {
13 def debugName = PRODUCT_NAME + "${defaultConfig.versionName}"
14 newApkName = debugName + "-debug.apk"
15 }
16 output.outputFile = new File(outputDir, newApkName)
17 }
18 }
19}
完整build.gradle文件内容如下:
1apply plugin: 'com.android.application'
2
3//产品名
4def PRODUCT_NAME = "BuglyDemo"
5
6android {
7 //添加签名文件配置
8 signingConfigs {
9 mysigns {
10 keyAlias 'alias'
11 keyPassword '123456'
12 storeFile file('sign.jks')
13 storePassword '123456'
14 }
15 }
16
17 compileSdkVersion 25
18 buildToolsVersion "25.0.2"
19
20 defaultConfig {
21 applicationId "com.gradle.demo"
22 minSdkVersion 11
23 targetSdkVersion 25
24 versionCode 1
25 versionName "1.0"
26 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
27 multiDexEnabled true //突破应用方法数65535的限制
28 }
29
30 //多渠道打包
31 productFlavors {
32 xiaomi {}
33 huawei {}
34 yingyongbao {}
35 wandoujia {}
36 }
37
38 //自动替换清单文件中的渠道号
39 productFlavors.all {
40 flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
41 }
42
43
44 buildTypes {
45 release {
46 minifyEnabled false //是否启用混淆
47 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
48 debuggable false
49 jniDebuggable false
50 signingConfig signingConfigs.mysigns
51 renderscriptDebuggable false
52 minifyEnabled false
53 pseudoLocalesEnabled false
54 zipAlignEnabled true
55 }
56 }
57
58//release包的命名格式为:产品名版本号渠道名.apk
59//debug包的命名格式为:产品名_版本号-debug.apk
60 applicationVariants.all { variant ->
61 variant.outputs.each { output ->
62 def outputFile = output.outputFile
63 if (null != outputFile && outputFile.name.endsWith('.apk')) {
64 File outputDir = new File(outputFile.parent);
65 def baseName = PRODUCT_NAME + "${defaultConfig.versionName}" + "_" + variant.productFlavors[0].name
66 def newApkName
67 if (variant.buildType.name.equals('release')) {
68 newApkName = baseName + '.apk'
69 } else if (variant.buildType.name.equals('debug')) {
70 def debugName = PRODUCT_NAME + "${defaultConfig.versionName}"
71 newApkName = debugName + "-debug.apk"
72 }
73 output.outputFile = new File(outputDir, newApkName)
74 }
75 }
76 }
77}
78
79dependencies {
80 compile fileTree(include: ['*.jar'], dir: 'libs')
81 androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
82 exclude group: 'com.android.support', module: 'support-annotations'
83 })
84 compile 'com.android.support:appcompat-v7:25.1.1'
85 testCompile 'junit:junit:4.12'
86 //添加友盟统计库依赖
87 compile 'com.umeng.analytics:analytics:latest.integration'
88}
打包工具:ApkChannelPackag
以上是关于Gradle实战的主要内容,如果未能解决你的问题,请参考以下文章