Android进阶修炼;Gradle骚操作合集
Gradle骚操作
Gradle是谷歌钦定的android项目构建工具。熟练使用gradle可以实现很多骚操作,比如多渠道打包,指定打包文件路径和文件名等,而且实现方式不止一种。Gradle工具的编程语言叫做 Groovy, Groovy的语法相对宽松,有点类似javascript,怎么写的人都有,但是最终目标可能是一致的。
本文收录一些Gradle骚操作供大家分享,经本人验证可行,会提供完整Demo(Gradle会存在新旧版本兼容问题,运行demo不要改动gradle版本配置):Demo地址
配置buildTypes{ xxx } 自定义"构建类型“
...
android {
...
// 配置签名文件
signingConfigs {
debug {
// 这里会使用默认签名
}
release {
storeFile file("mykey.jks")
storePassword "android"
keyAlias "android"
keyPassword "android"
v2SigningEnabled true
}
}
//level 1: 打包方式,默认有debug和release,当然可以自己加喜欢的
buildTypes {
//内置 debug 和 release,但是我们可以忽略不计
uat {
// 测试环境可调试
debuggable true
signingConfig signingConfigs.release
}
prd {
// 正式环境 可调式
debuggable true
signingConfig signingConfigs.release
}
online {
// 正式发布包 不可调试
debuggable false
signingConfig signingConfigs.release
}
}
}
dependencies {
...
}
关键点:
-
配置buildTypes之前,必须 配signingConfigs,把签名文件配好,否则,就会出现 install Task缺失的情况
image -
buildTypes里面默认就有 debug和release,这一点在as里面可以看到,下图中另外3个是我们自己加的type:
-
通过指定的buildType打包出来的apk,可以读到 当前包的buildType,并且用于app程序内部,比如配置app的网络环境设置(测试环境和正式环境),还有 是否支持日志打印(屏蔽所有日志和放开所有日志),获取的方式为
BuildConfig.BUILD_TYPE
:class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) tvBuildType.text = BuildConfig.BUILD_TYPE } }
配置productFlavors{ xxx } 自定义"多种打包风味“
Demo地址:https://github.com/18598925736/EnjoyGradleHank/commits/master(切换到时间点:78dc2ca
)
打包风味 flavor 是 谷歌提供的 多渠道打包的官方手段。
app module 的 build.gradle
...
android {
...
//level 2: 通过productFlavors打包配置,实现多维度风味打包
flavorDimensions "zone", "themeColor"
// 定义多风味
productFlavors {
/**
* 越南版 每一个风味选项都必须指定独立的风味值
*/
vn {
applicationId "com.global.vn.ftint"
dimension "zone"
manifestPlaceholders = [zone: "vn"]
}
/**
* 国内版
*/
cn {
applicationId "com.global.cn.ftint"
dimension "zone"
manifestPlaceholders = [zone: "cn"]
}
/**
* 主题风格为红色
*/
red {
dimension "themeColor"
manifestPlaceholders = [themeColor: "red"]
}
/**
* 主题风格为蓝色
*/
blue {
dimension "themeColor"
manifestPlaceholders = [themeColor: "blue"]
}
}
}
dependencies {
...
}
同时必须与 manifest配合:
app module 的 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhou.enjoygradle2">
<application
...>
...
<meta-data
android:name="ZONE"
android:value="${zone}" />
<meta-data
android:name="themeColor"
android:value="${themeColor}" />
</application>
</manifest>
关键点:
-
多风味打包支持多维度,定义维度是在
flavorDimensions "zone", "themeColor"
这样代码中 , 此处定义了两个维度:“地区”,“主题颜色” -
配置 风味 productFlavor的时候必须指定维度:以下代码就是指定维度为 zone ,维度的值,则是 cn,同时还指定了包名applicationId.
cn { applicationId "com.global.cn.ftint" dimension "zone" manifestPlaceholders = [zone: "cn"] }
-
多种维度的笛卡尔积的数值,就是 打包方式的总个数,比如上方,zone维度2个风味,themeColor维度2个风味,所以风味的总数是 2x2=4,在as中也能看到对应的task,但是,此处的 维度总数 和 之前的buildType又形成了一个新的笛卡儿积,之前有5个(uat,prd,online,还有自带的 debug和release ),所以能看到的task一共是 4x5=20
改变apk输出路径和文件名
目前查到有三种写法:
-
重写
applicationVariants.all{}
参数闭包,支持绝对路径的写法... android { ... //level3 applicationVariants.all { variant -> //这个修改输出的APK路径 if (variant.buildType.name != "debug") {//防止AS无法安装debug包(apk) variant.getPackageApplication().outputDirectory = new File(myPackageDir()) } variant.getPackageApplication().outputScope.apkDatas.forEach { apkData -> //这个修改输出APK的文件名 apkData.outputFileName = "${appNamePrefix()}" + variant.versionName + "_" + "${releaseTime()}" + "_" + variant.flavorName + "_" + variant.buildType.name + "_" + ".apk" } } } def myPackageDir(){ return "C:\\Users\\adminstrator\\Desktop\\myPackage" } def appNamePrefix() { return "HankZhou" } String releaseTime() { Date date = new Date() String dates = date.format("yyyyMMdd_hhmmss", TimeZone.getTimeZone("UTC")) return dates } dependencies { ... }
执行
gradlew assembleCnBlueOnline
之后在 电脑桌面上的 myPackage 文件夹中会存在一个apk:
-
重写
applicationVariants.all{}
参数闭包,支持相对路径的写法... android { ... // 或者,使用这种方式,只支持相对路径 applicationVariants.all { variant -> String time = new Date().format('yyyyMMdd_HHmmss') String pkgName = "enjoy_${time}_v${defaultConfig.versionName}_${buildType.name}_${flavorName}.apk" outputs.first().outputFileName = "../../../${pkgName}" // 它不能使用绝对路径,也就是说可以使用项目的相对路径 } } ... dependencies { ... }
执行
gradlew assembleCnBLueUat
之后,app module的build目录内会出现 apk:
-
通过干涉task的执行过程
... android { ... } /** * 当有序任务图中加进去一个任务的时候,对它进行处理,doLast重新定义完成之后的行为 */ tasks.whenTaskAdded { task -> if (task.name.equalsIgnoreCase("assembleCnBlueUat")) { println("=============发现任务 ${task.name}") // 如果是assembleRelease任务,在最后执行导出apk以及mapping目录到指定目录 task.doFirst { println("=============doFirst") } task.doLast { outputReleaseFile() } } } void outputReleaseFile() { println("=============outputReleaseFile") android.applicationVariants.all { variant -> // 如果是正式版打包 if (variant.name.contains("cnBlueUat")) { println("=============准备拷贝${variant.name}") File outputPath = new File(myPackageDir()) println("==========把文件${variant.outputs[0].outputFile} \n 拷贝到 目标位置:$outputPath") outputPath.mkdir() // 但是很明显,这里的copy并没有成功 copy {// 拷贝apk文件 println("========开始拷贝...") from variant.outputs[0].outputFile into outputPath // 重命名导出名称 rename { appNamePrefix() + variant.name + '_' + android.defaultConfig.versionName + '-' + releaseTime() + ".apk" } } } } } def myPackageDir(){ return "C:\\Users\\adminstrator\\Desktop\\myPackage" } def appNamePrefix() { return "HankZhou" } String releaseTime() { Date date = new Date() String dates = date.format("yyyyMMdd_hhmmss", TimeZone.getTimeZone("UTC")) return dates } dependencies { ... }
执行
gradlew assembleCnBlueUat
之后,电脑桌面上会出现 mypackage目录,内部有一个apk
方式对比
- 重写
applicationVariants.all{}
参数闭包的方式,修改输出的绝对路径,或者相对路径,分别对应两个api:- 相对 outputs.first().outputFileName
- 绝对 variant.getPackageApplication().outputScope.apkDatas 和 apkData.outputFileName
- 使用 干涉task任务的方式,和 上面的方式比起来,写法更加复杂,但是可控制的精度也更高,比如:它除了可以改变输出路径之外,还可以直接指定拦截哪些task,而不是像 重写
applicationVariants.all
闭包 一样,一律变更输出路径和文件名。
先写这么多。后续还有继续补上
作者:波澜步惊
链接:https://www.jianshu.com/p/94933c63ecb2