Android使用Gradle命令动态传参完成打包,不需要修改代码

时间:2022-09-30 22:35:12

不得不说,Gradle很强大,有人会问Gradle是什么?这里也不细讲,在我认为他就是一个构建神器。Gradle 提供了:

  • 一个像 Ant 一样的非常灵活的通用构建工具
  • 一种可切换的, 像 Maven 一样的基于合约构建的框架
  • 支持强大的多工程构建
  • 支持强大的依赖管理(基于 ApacheIvy )
  • 支持已有的 Maven 和 ivy 仓库
  • 支持传递性依赖管理, 而不需要远程仓库或者 pom.xml 或者 ivy 配置文件
  • 基于 Groovy 的构建脚本
  • 有丰富的领域模型来描述你的构建

build.gradle文件

  首先先来说说这个文件,大家都知道Android 项目默认目录下就有两个build.gradle文件,其实也类似Maven中的pom.xml文件,一个是Project范围的,另一个是Module范围的,由于一个Project可以有多个Module,所以每个Module下都会对应一个build.gradle。
  这两个文件是有区别的,Project下的build.gradle是基于整个Project的配置,而Module下的build.gradle是每个模块自己的配置。这里我主要讲一下module下的build.gradle文件,也就是通常说的默认Module app下的。
  讲到这个配置,还需要引入Gradle中的Task概念。Gradle的Project从本质上说只是含有多个Task的容器,一个Task与Ant的Target相似,表示一个逻辑上的执行单元。我们可以通过很多种方式定义Task,所有的Task都存放在Project的TaskContainer中,Task是Gradle的第一公民。
  Task可以自定义,但如果没有什么太大需求,其实几乎都用不到,因为Gradle会根据你的build.gradle自动创建Task,你只需要在配置文件里面配置一些需要的就可以了,在构建的时候会自动生成Task,用Android Studio的点击同步也会自动生成。
  可以打开Android Studio右侧的Gradle面板,双击就可以执行Task

Android使用Gradle命令动态传参完成打包,不需要修改代码

  命令行里面可以直接使用gradle taskName(例如: gradle assemble360会将360市场的所有包都打出来,包括debug,release等,当然,这些还得你先在build.gradle里面配置好)。Android Studio 中也可以打开左下角的terminal中使用gradlew 执行命令,gradlew是gradle wrapper的简写,和gradle命令功能一样。

Android使用Gradle命令动态传参完成打包,不需要修改代码

  好,开始切入正题,假如现在有这样的需求:

  • 通常我们的应用都会有开发环境(也可以理解为debug环境)、测试环境、预发环境、正式环境区分,我想要不改代码就可以打出我想要环境的包。比如我现在分别想要一个测试环境的包和一个线上环境的包,但是我又不想改代码
  • 发布的时候发现版本号和版本名忘记改了
  • 我想要随时指定一个目录,将打包好的文件放在这里面
  • 我想要在打包时可以自定义安装包的文件名

  很简单,如果你不想改代码又想要得到不同环境的包,那当然是使用Gradle的命令,前面说过Gradle命令后面可以加上Task的name直接执行Task,那我们可以自己定义我们需要的Task,让不同的Task去做我们想要做的事不就解决问题了吗。
  可是下面又说要动态指定版本号版本名文件名和文件输出路径,那怎么办?
  也不难,传参,需要什么就传入什么,这样就解决了动态指定的问题了。

  思路讲到这里,我们来看看具体要怎么配置这个文件:

第一个问题:怎么去配置不同环境的Task?

  原先网络请求路径可能很多人都会写在代码里面,如下图所示

/**
* 存放一些全局常量
*/
public class Constants {
//外网测试环境
public static final String BASEHTTP = "http://test.api.cn";
//线上地址
// public static final String BASEHTTP = "http://release.api.cn";
//预发环境
// public static final String BASEHTTP = "http://pre.api.cn";
//本地测试
// public static final String BASEHTTP = "http://dev.api.cn"; //登录
public static final String LOGIN_URL = BASEHTTP + "/api/user/login";
}

在需要更换环境的时候就换一个BASEHTTP的值,这样可以解决问题,但是每一次编译打包都需要重新去改一下代码。一两个包还好,如果多了就会觉得很麻烦,不方便。 
  所以就想到了可不可以将这些信息都写在build.gradle配置文件里面,这样好像就可以跟Gradle有点挂钩了

//正式环境
def API_RELEASE_HOST = "\"http://release.api.cn\""
//预发环境
def API_PRE_RELEASE_HOST = "\"http://pre.api.cn\""
//测试环境
def API_TEST_HOST = "\"http://test.api.cn\""
//开发环境
def API_DEV_HOST = "\"http://dev.api.cn\""

Gradle脚本是用Groovy语言来写的,Groovy语言这里不细讲,大家可以网上搜Groovy语法,资料还是蛮多的,使用Groovy可以感受到到以下两个特点:

  • Groovy继承了Java的所有东西,就是你突然忘了Groovy的语法可以写成Java代码,也就是Groovy和Java混在一起也能执行。
  • Groovy和Java一样运行在JVM,源码都是先编译为class字节码。

  这里我用def定义了几个常量,分别用来表示不同的环境的请求地址,然后在defaultConfig里面自定义了一个常量名,作为代码与配置文件的桥梁,建立了连接。注意:这里的字符串需要在里面加入引号,用转义符转义,因为Groovy会直接把最外层引号内的值赋值给生成的自定义变量,如果不加,赋值后的String字符串就会没有引号,导致编译出错。

 defaultConfig {
applicationId "com.test"
minSdkVersion
targetSdkVersion
versionCode
versionName 1.1.
buildConfigField("String", "API_HOST", "${API_DEV_HOST}")
}

这里的buildConfigField就是自定义一个常量,第一个参数表示类型,第二参数表示常量名,第三个参数传入的是值。
  点击同步后在代码中就可以直接调用BuildConfig.API_HOST来使用了,因为当点击同步后,Gradle就会在BuildConfig这个类中加入常量API_HOST

public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.test";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = ;
public static final String VERSION_NAME = "1.1.1";
// Fields from default config.
public static final String API_HOST = "http://test.api.cn";
}

可以看到BuildConfig这个类中的最后一行已经有了API_HOST这个常量了,还有一些其他的常量也是根据配置自动生成的,这里可以先不用管。 
  现在可以通过代码请求到配置文件里面的配置了。

 public static final String LOGIN_URL = BuildConfig.API_HOST + "/api/user/login";

接下来要做的就是怎么执行不同的task就会引用不同的配置。
  build.gradle文件中有一个buildTypes,里面放的是你在build的时候需要选择的类型,默认有一个debug,也可以自己自定义,我在这里加了四种类型,debug(开发)、beta(测试)、preRelease(预发)、release(正式发布)

buildTypes {
/* 线上环境 */
release {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
buildConfigField "String", "API_HOST", "${API_RELEASE_HOST}"//API Host
minifyEnabled true //是否混淆
//是否设置zip对齐优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//签名
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} /* 预发环境 */
preRelease {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
buildConfigField "String", "API_HOST", "${API_PRE_RELEASE_HOST}"//API Host
minifyEnabled true //是否混淆
//是否设置zip对齐优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//签名
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} /* 本地开发环境 */
debug {
minifyEnabled false
} /* 测试环境 */
beta {
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
buildConfigField "String", "API_HOST", "${API_TEST_HOST}"//API Host
minifyEnabled true //是否混淆
//是否设置zip对齐优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//签名
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }

可以看到,在每个buildType里面都有相应的配置,你打哪个类型的包,就会去读取哪个类型的配置,如果没有,默认会去读取defaultConfig里面的配置,defaultConfig里面相当于初始值,这样就做到了不同环境有了不同的配置,同步一下,再看一下Android Studio右侧的Gradle面板,可以发现多了一些Task,自动生成了一些Task,比如原先是assembleDebug,现在就多了assembleBeta、assemblePreRelease、assembleRelease,想要执行哪个环境就执行哪个任务就ok了。
  但是很多第三方的外部包配置不止在build.gradle文件,还会在AndroidManifest.xml做一些正式环境和测试环境的区分,做一些不同的配置,这里的配置怎么处理?

第二个问题:怎么将AndroidManifest.xml里面的配置在build.gradle里面进行配置?

  举个例子,我这里拿talkingData的配置来说,需要在AndroidManifest.xml里面指定APP_ID

<!--TalkingData 配置-->
<meta-data
android:name="TD_APP_ID"
android:value="7E5389EAD0C2324FB7B379701F6D2BA0" />

包括百度地图、个推等其实很多第三方库都需要配置这些,在AndroidManifest.xml里面可以直接引用build.gradle文件里面的配置,build.gradle里面怎么配置我们一会再讲,先看看引用配置后代码:

    <!--TalkingData 配置-->
<meta-data
android:name="TD_APP_ID"
android:value="${TALKING_DATA_APP_ID}" />

这里使用了引用了build.gradle里面的TALKING_DATA_APP_ID的值,我们再来看看build.gradle文件里面怎么配置。

def TEST_TALKING_DATA_APP_ID = "6E5389EAD0C2C2CFB7B379701F6D2BA8"

    defaultConfig {
applicationId "com.test"
minSdkVersion
targetSdkVersion
versionCode
versionName 1.1.
buildConfigField("String", "API_HOST", "${API_DEV_HOST}")
manifestPlaceholders = [
/* talkingData 测试环境 */
TALKING_DATA_APP_ID: "${TEST_TALKING_DATA_APP_ID}"
]
}

我在defaultConfig里面指定了一个manifestPlaceholders属性,也是gradle默认就提供的一个属性,从形式可以看出是一个数组的形式,里面可以写多个键值对,用逗号隔开,AndroidManifest.xml会从manifestPlaceholders数组里面去寻找匹配的键,找到了就会引用这个键所对应的值。
  这样问题就迎刃而解了,所有的AndroidManifest.xml里面的配置都可以写在build.gradle里面统一处理了。
  而上面说过,defaultConfig是默认的配置,不同的buildType可以指定不同的配置,所以在不同的buildType,也可以理解为不同的环境里面配置不同的manifestPlaceholders就可以了。代码如下:

 buildTypes {
/* 线上环境 */
release {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
buildConfigField "String", "API_HOST", "${API_RELEASE_HOST}"//API Host
manifestPlaceholders = [
/* release 环境 */
GETUI_APP_ID : "${RELEASE_GETUI_APP_ID}",
GETUI_APP_KEY : "${RELEASE_GETUI_APP_KEY}",
GETUI_APP_SECRET : "${RELEASE_GETUI_APP_SECRET}",
/* talkingData release 环境 */
TALKING_DATA_APP_ID: "${RELEASE_TALKING_DATA_APP_ID}",
PACKAGE_NAME : defaultConfig.applicationId
]
minifyEnabled true //是否混淆
//是否设置zip对齐优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//签名
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} /* 预发环境 */
preRelease {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
buildConfigField "String", "API_HOST", "${API_PRE_RELEASE_HOST}"//API Host
manifestPlaceholders = [
/* release 环境 */
GETUI_APP_ID : "${RELEASE_GETUI_APP_ID}",
GETUI_APP_KEY : "${RELEASE_GETUI_APP_KEY}",
GETUI_APP_SECRET : "${RELEASE_GETUI_APP_SECRET}",
/* talkingData release 环境 */
TALKING_DATA_APP_ID: "${RELEASE_TALKING_DATA_APP_ID}",
PACKAGE_NAME : defaultConfig.applicationId
]
minifyEnabled true //是否混淆
//是否设置zip对齐优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//签名
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} /* 本地开发环境 */
debug {
minifyEnabled false
} /* 测试环境 */
beta {
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
buildConfigField "String", "API_HOST", "${API_TEST_HOST}"//API Host
manifestPlaceholders = [
/* 个推测试环境 */
GETUI_APP_ID : "${TEST_GETUI_APP_ID}",
GETUI_APP_KEY : "${TEST_GETUI_APP_KEY}",
GETUI_APP_SECRET : "${TEST_GETUI_APP_SECRET}",
/* talkingData 测试环境 */
TALKING_DATA_APP_ID: "${TEST_TALKING_DATA_APP_ID}",
PACKAGE_NAME : defaultConfig.applicationId
]
minifyEnabled true //是否混淆
//是否设置zip对齐优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//签名
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }

我们已经可以执行不同的Task去打不同环境的包了。
  命令行里面可以使用gradle assembleBeta(assembleBeta表示测试环境,其他环境可以替换后面的名字,如assemblePreRelease、assembleRelease等,如果配置了渠道,还会在assemble后面拼上渠道名,例如我的渠道名是360,我要打release的包,那就是assemble360Release)。
  ide里面可以点击菜单上的build,再点击Generate Signed APK…,填完keystore密码后选择buildType进行打包操作。
  提醒:在打包前最好先做一下clean操作,否则会出现有些代码打包不进去,不知道其他人是不是这样的。

第三个问题:怎么动态传参满足需求?

很简单,直接上代码:

 defaultConfig {
applicationId "com.ixwork"
minSdkVersion
targetSdkVersion
//关键看这两行
versionCode project.hasProperty('VERSION_CODE') ? Integer.parseInt(VERSION_CODE) : DEF_VERSION_CODE
versionName project.hasProperty('VERSION_NAME') ? VERSION_NAME : "${DEF_VERSION_NAME}"
buildConfigField("String", "API_HOST", "${API_DEV_HOST}")
}

关键看versionCode 和versionName这两行,原先默认是直接在后边写上版本号和版本名,这里用了三目运算符,可以用project.hasProperty(‘KEY’)来判断是否有KEY这个参数传入,如果有的话就就返回true,就会使用传入的值作为实际值,这里用了强转,将传入的String类型转为int类型的,如果没有就会返回false,使用默认的值。

  同理,传入文件名和文件输出路径也一样。

//修改生成的最终文件名
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
//判断是否有这个OUT_PUT_DIR参数传入
File outputDirectory = new File(project.hasProperty('OUT_PUT_DIR') ? OUT_PUT_DIR : outputFile.parent);
def fileName
if(!project.hasProperty('FILE_NAME')){
if (variant.buildType.name == "release" || variant.buildType.name == "preRelease") {
// 输出apk名称为app_v1.0.0_2015-06-15_playStore.apk
fileName = "app_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}_${variant.buildType.name}.apk"
} else if (variant.buildType.name == "beta") {
fileName = "app_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}.apk"
} else {
fileName = outputFile.name
}
}else{
fileName = FILE_NAME
}
// println("输出apk ---> " + outputDirectory.absolutePath + File.separator + outputFile.name)
output.outputFile = new File(outputDirectory, fileName)
}
}
}

因为我这里配置了多个渠道,所以使用variant.outputs.each循环输出文件,在里面分别处理每一个包。
  在代码里面分别用了project.hasProperty(‘OUT_PUT_DIR’)和project.hasProperty(‘FILE_NAME’)来判断是否有这个参数,有无参数分别做了不同的处理。
  fileName = “app_v{defaultConfig.versionName}_{defaultConfig.versionName}_{releaseTime()}_{variant.productFlavors[0].name}_{variant.productFlavors[0].name}_{variant.buildType.name}.apk”
这里对文件名进行了比较人性化的处理,加上了各种信息,通过包名就可以看出一些基本信息,如果不指定名字传入,就会使用这个默认的名字。

  万事俱备,只欠东风了。到这里,基本所有都说完了,最后还有一个问题,哈哈,如何传参?

第四个问题:如何传参?

 gradle clean assembleBeta -PVERSION_CODE= -PVERSION_NAME=1.1. -POUT_PUT_DIR=/home/user/Desktop -PFILE_NAME=test.apk

 在命令行里面执行这个命令就可以打出所有的Beta包了(前提是已经安装好Gradle,并配置好Gradle的环境变量,或者使用IDE里面的terminal,在项项目目录下使用gradlew命令),其中assembleBeta 可以根据自己需求替换成其他的task名字。
  传参就是在后面加上 -P参数,-P后面再加上要传入的键值对,中间用=号连接,需要什么参数就传什么参数,如果有其他需要也可以自定义加入。

最后附上build.gradle源文件,可以点击此处下载 build.gradle

另外还有一篇关于Android使用Gradle配合Jenkins自动构建打包的文章,有兴趣的可以去看看。
http://blog.csdn.net/u014637428/article/details/52248589

以上是我的配置过程,如有问题欢迎留言,互相学习。

---------------------

本文来自 githing 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/u014637428/article/details/52249423?utm_source=copy

Android使用Gradle命令动态传参完成打包,不需要修改代码的更多相关文章

  1. 在Java中动态传参调用Python脚本

    最近,又接触到一个奇葩的接口,基于老板不断催促赶时间的情况下,在重写java接口和复用已有的python脚本的两条路中选择了后者,但是其实后者并没有好很多,因为我是一个对python的认识仅限于其名称 ...

  2. &lbrack;Android&rsqb; 配置build&period;gradle 动态传参

    (1)一个Android工程中有一个build.gradle是负责Project范围的,而Module中又有各自的build.gradle是专门负责模块的. (2)在Gradle中Task是一等公民, ...

  3. js函数动态传参

    js函数体内可以通过arguments对象来接收传递进来的参数,利用这一对象属性可以动态传参. function box() { return arguments[0]+' | '+arguments ...

  4. uploadify的用法与动态传参 提供demo下载

    ---恢复内容开始--- 官网:http://www.uploadify.com/   一款不错的上传插件.官方文档http://www.uploadify.com/documentation/ 用法 ...

  5. Postman 串行传参和动态传参详解

    Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件 用Postman做接口测试的时候,要把多条用例一起执行,就需要把用例连接起来,一次性执行 目录 串行传参 动态传参 使用 ...

  6. python之路--动态传参&comma;作用域&comma;函数嵌套

    一 . 动态传参(重点)  * ,  ** * 与 ** * 在形参位置. * 表示不定参数, 接收的是位置参数 接收到的位置参数的动态传参: 都是元组 def eat(*food): # 在形参这里 ...

  7. python----函数的动态传参

    函数的动态传参 *args 将所有的实参的位置参数聚合到一个元组,并将这个元组赋值给args 有些时候,对于函数,传入的实参数量可能是不固定的,也就是动态的,这个时候我们就需要用到函数的动态传参.下面 ...

  8. python函数的动态传参&period;作用域与命名空间

    一.动态传参1.*表示动态传参. 可以接受所有的位置参数传参的时候自动的把实参打包成元组 交给形参 def chi(*food): print(food) chi() # 动态传参可以不传参数 chi ...

  9. python记录&lowbar;day10 动态传参 命名空间 作用域

    一.动态传参 动态传参用到 *args 和 **kwargs ,*号表示接收位置参数,args是参数名:**表示接收关键字参数,kwargs是参数名 def chi(*food): print(foo ...

随机推荐

  1. 机顶盒Demux

    主页http://www.videolan.org/vlc/ 机顶盒软件开发仿真平台的设计与实现http://max.book118.com/html/2012/0311/1260745.shtm

  2. C&num;类、静态类、静态变量,初始化执行顺序

    执行顺序: 类的静态变量 ↓ 类的静态构造函数 ↓ 类的普通变量 ↓ 基类的静态变量 ↓ 基类的静态构造函数 ↓ 基类的普通变量 ↓ 基类的构造函数 ↓ 类的构造函数

  3. 【转】oracle的substr函数的用法

    [转]oracle的substr函数的用法 )     would return 'The' ) value from dual

  4. C指针

    1,每行最大长度,处理的最大列号; preprocessor directives,preprocessor,预处理器读入源代码,根据预处理指令对其进行修改,把修改后 的源代码递交给编译器; 预处理器 ...

  5. HUB主要芯片方案

    HUB主要品牌:慧荣.擎泰.联盛  安国.创惟 创惟GL850G简介:拥有低耗电.温度低及接脚数减少等产品特性.它支援4个下游连接埠,采用48 pin LQFP封装,可完全支援USB 2.0/1.1规 ...

  6. Android studio导入第三方类库

    1.开发过程中想要导入第三方类库和Eclipse也是有差别的,我们导入SlidingMenu这个类库,从github上下载下来解压到项目目录下. 2.然后我们重启我们的android studio就会 ...

  7. Altium Designer 快速修改板子形状为Keep-out layer大小

    Altium Designer 快速修改板子形状为Keep-out layer大小 1,切换到 Keep-out layer层, 2,选择层,快捷键为S+Y: 3,设计>>板子形状> ...

  8. MySQL命令无法结束

    输入完sql语句后 输入分号结束,发现无法结束,原因一般是引号不对称导致的.再输入'; 或者对称的分号就可以结束.

  9. mongodb参数

    启动命令 : mongod -port --dbpath data/ --logpath log/mongodb.log --fork ps -ef | grep momgod (查看是否启动成功) ...

  10. 转&colon;&sol;&sol;创建oracle索引时需要注意的7个事项

    在创建Oracle索引时,有一些问题使我们需要注意的,下面就为您介绍创建oracle索引的一些注意事项,希望对您学习创建Oracle索引方面能有所帮助. 1.一般来说,不需要为比较小的表创建索引: 2 ...