本篇具体介绍如何用 Gradle 构建 Android 工程项目;有关Gradle的基本语法可以参见另一篇文章:http://blog.csdn.net/wangbaochu/article/details/51177672
Gradle本身支持直接签名,只需要在releas部分添加如下代码即可:
signingConfigs {
debug {
}
release {
storeFile file("../yourapp.keystore")
storePassword "your password"
keyAlias "your alias"
keyPassword "your password"
}
}
buildTypes {
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
}
release {
minifyEnabled true
//字节码对齐
zipAlignEnabled true
//移除无用的资源文件
shrinkResources true
//签名
signingConfig signingConfigs.release
//混淆编译
//此处第一个文件指的是android_sdk/tools/proguard/proguard-android.txt
//第二个文件是指你当前工程的混淆配置文件,其路径需与build.gradle在同一目录,否则还要加上相对路径
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
一般填上上面的代码即可执行签名,但是这种方式不太安全,建议不要在build.gradle文件中写上签名文件的密码,因为build.gradle文件一般都会集成到代码的版本控制中,这样所有人都会有签名文件的密码。
所以应该把签名文件的密码隔离起来,写到一个配置文件中,此配置文件不包含在代码版本控制中,这样其他开发者就不会知道签名文件的密码。gradle配置文件一般以.properties结束,我们先新建一个signing.properties文件,内容如下:
STORE_FILE=yourapp.keystore
STORE_PASSWORD=your password
KEY_ALIAS=your alias
KEY_PASSWORD=your password
接下在guild.gradle文件中读取signing.properties配置文件,读取的代码如下:
File propFile = file('signing.properties');
if (propFile.exists()) {
def Properties props = new Properties()
props.load(new FileInputStream(propFile))
if (props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
} else {
android.buildTypes.release.signingConfig = null
}
} else {
android.buildTypes.release.signingConfig = null
}
由于国内Android市场众多渠道,为了统计每个渠道的下载及其它数据统计,就需要我们针对每个渠道单独打包。gradle的多渠道打包很简单,因为gradle已经帮我们做好了很多基础功能。下面以友盟统计为例说明,一般友盟统计在AndroidManifest.xml里面会有这么一段声明:
其中CHANNEL_ID就是友盟的渠道标示,多渠道的实现一般就是通过修改CHANNEL_ID值来实现的。接下来将一步一步来实现多渠道版本打包。
1.在AndroidManifest.xml里配置meta-data,然后在build.gradle文件中利用manifestPlaceholders来替换成自己想要设置的值。这样在java代码中就通过pacakgeManager读取AndroidManifest的meta-data拿到最新的值。
2.在build.gradle设置productFlavors,修改manifestPlaceholders的值(manifestPlaceholders是Gradle默认提供的)
productFlavors {
playStore {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "playStore"]
}
miui {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "miui"]
}
wandoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
}
}
按照上面两步即可编译打多渠道包了,命令是 ./gradlew assembleRelease,可以打包所有的多渠道包。如果只是想打单渠道包,则执行相应的task即可,如gradle assemblePalyStoreRelease就是打PlayStore渠道的Release版本。
3.如果希望可以对最终的文件名做修改,如需要针对不同的需求生成不同的文件。而修改文件名也很简单,参考以下代码即可实现
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
android{
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
File outputDirectory = new File(outputFile.parent);
def fileName
if (variant.buildType.name == "release") {
fileName = "app_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
} else {
fileName = "app_v${defaultConfig.versionName}_${packageTime()}_debug.apk"
}
output.outputFile = new File(outputDirectory, fileName)
}
}
}
}
buildTypes {
debug {
buildConfigField "boolean", "LOG_DEBUG", "true"//是否输出LOG信息
buildConfigField "String", "API_HOST", "\"http://api.test.com\""//API Host
}
}
然后就可以在java代码中通过BuildConfig.LOG_DEBUG或者BuildConfig.API_HOST调用了。BuildConfig.java是Gradle编译自动生成的一个类,会打包到我们的APK中。
android {
defaultConfig {
// dex突破65535的限制
multiDexEnabled true
}
}
2.然后就是引入multidex库文件
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
3.最后就是你的AppApplication继承一下MultiDexApplication即可。
可以参考另外一篇文章:http://blog.csdn.net/wangbaochu/article/details/46536635
// 声明是Android程序
apply plugin: 'com.android.application'
// 定义一个打包时间
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
android {
// 编译SDK的版本
compileSdkVersion 21
// build tools的版本
buildToolsVersion '21.1.2'
defaultConfig {
// 应用的包名
applicationId "com.**.*"
minSdkVersion 14
targetSdkVersion 21
versionCode 1
versionName "1.0"
// dex突破65535的限制
multiDexEnabled true
// 默认是umeng的渠道
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
}
// 移除lint检查的error
lintOptions {
abortOnError false
}
//签名配置
signingConfigs {
debug {
// No debug config
}
release {
storeFile file("../yourapp.keystore")
storePassword "your password"
keyAlias "your alias"
keyPassword "your password"
}
}
buildTypes {
debug {
// buildConfigField 自定义配置默认值
buildConfigField "boolean", "LOG_DEBUG", "true"
buildConfigField "String", "API_HOST", "\"http://api.test.com\""//API Hos
versionNameSuffix "-debug"
minifyEnabled false
//是否zip对齐
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
}
release {
// buildConfigField 自定义配置默认值
buildConfigField "boolean", "LOG_DEBUG", "false"
buildConfigField "String", "API_HOST", "\"http://api.release.com\""//API Host
//// 是否进行混淆
minifyEnabled true
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//混淆规则文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// 输出apk名称为boohee_v1.0_2015-06-15_wandoujia.apk
def fileName = "boohee_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}
}
// 友盟多渠道打包
productFlavors {
wandoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
}
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
}
tencent {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "tencent"]
}
taobao {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "taobao"]
}
}
}
dependencies {
// 编译libs目录下的所有jar包
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:21.0.3'
compile 'com.jakewharton:butterknife:6.0.0'
}
如果每次修改上面gradle进行多渠道打包很不方便,下面我们可以编写一个shell脚本,通过shell脚本灵活配置gradle:
##################################################################################################
# 文件名:build.sh
# 输入的命令格式为:$ sh ./build.sh Online channelId release
# 参数1: Online 或者 Test 分别表示:线上环境、测试环境
# 参数2: channelId表示渠道号,编译出来的APK的AndroidManifest.xml中UMENG_CHANNEL_VALUE的值即为该channelId
#
# 参数3:表示编译出来的APK是debug版还是release版
#################################################################################################
#!/bin/sh
echo "param1 : $1 , param2 : $2 , param3 : $3"
#打包脚本
if [ "$3" = "Debug" ];then
#编译debug版,参数1分别表示 Online 或者 Test
gradle_arg="assemble${1}Debug"
else
#编译release版,参数1分别表示 Online 或者 Test
gradle_arg="assemble${1}Release"
fi
#获取渠道号
channels =$2
echo “CHANNELS: $channels"
if [ "$channels" = "" ]; then
echo "not found channels"
echo "build failure"
exit 1
fi
channels =$(echo $channels | tr "," "\n")
#JDK默认配置是1.6,java范型编译不过,需要指定JDK1.7的路径
#检测JDK版本是否为1.6,只要不是1.6肯定就是1.7及以上
JDK_MAIN_VERSION=1.6.
JDK_VERSION_6=`java -version 2>&1 | grep ${JDK_MAIN_VERSION}`
echo "JDK Version = ${JDK_VERSION_6}"
#打包的函数
packageApk(){
if [ "${JDK_VERSION_6}" = "" ]; then
sh ./gradlew $gradle_arg -Pchannel=$1
else
sh ./gradlew -Dorg.gradle.java.home=/usr/alibaba/jdk1.7.0_25 $gradle_arg -Pchannel=$1
fi
}
#先调用gradlew clean一下
if [ "${JDK_VERSION_6}" = "" ]; then
sh ./gradlew clean
else
sh ./gradlew -Dorg.gradle.java.home=/usr/alibaba/jdk1.7.0_25 clean
fi
#循环编译出所有渠道的APK
for x in $channels
do
packageApk $x
if [ $? -eq 0 ];then
echo "build $1 success"
else
echo "build $1 failure"
exit 1
fi
done
echo "build completed---------------------------------"
build.gradle修改如下
productFlavors {
//替换AndroidManifest.xml的字符串为渠道名称
def myChannel;
if (!project.hasProperty(“channel”)) {
myChannel = "default"
} else {
myChannel = channel
}
// SERVER_ENV表示运行环境,线上0,测试1
online {
manifestPlaceholders = [SERVER_ENV:"0",UMENG_CHANNEL_VALUE:"${myChannel}"]
}
test {
manifestPlaceholders = [SERVER_ENV:"1",UMENG_CHANNEL_VALUE:"${myChannel}"]
}
}
AndroidManifest.xml修改如下
Java代码中的调用:
private static String sUmengChannel = null;
public static String getChannel() {
if (TextUtils.isEmpty(sUmengChannel)) {
try {
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA);
Bundle meta = appInfo.metaData;
if (meta.containsKey("UMENG_CHANNEL")) {
sUmengChannel = meta.get("UMENG_CHANNEL").toString();
}
if (TextUtils.isEmpty(sUmengChannel)) {
sUmengChannel = "default";
}
} catch (Exception e) {
e.printStackTrace();
}
}
return sUmengChannel;
}
public static initEnvironment() {
try {
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA);
Bundle meta = appInfo.metaData;
if (meta != null && meta.containsKey("run_env")) {
switch (meta.getInt("run_env")) {
case 0:
//线上环境
break;
case 1:
//测试环境
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}