本篇具体介绍如何用 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.keystoreSTORE_PASSWORD=your passwordKEY_ALIAS=your aliasKEY_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)            }        }    }}

三、buildConfigField自定义配置

大家可能会遇到下面这种情况,就是Beta版本服务器和Release版本服务器通常不在一台服务器上,而测试希望可以同时发布两个服务器的版本用于测试,这个时候我们就需要修改代码,然后一个一个老老实实的发包。gradle提供buildConfigField配合多渠道打不同服务器版本的方法。其实用法很简单,首先在相应的节点加上定义,比如:
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中。

四、dex突破65535的限制

随着项目的一天天变大,慢慢的都会遇到单个dex最多65535个方法数的瓶颈,如果是ANT构建的项目就会比较麻烦,但是Gradle已经帮我们处理好了,而添加的方法也很简单,总共就分三步 :
1.首先是在defaultConfig节点使能多DEX功能
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

五、完整的gradle脚本

一份项目中使用的完整的gradle文件配置:

// 声明是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'}

六、Shell脚本调用Gradle

如果每次修改上面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/shecho "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 =$2echo “CHANNELS: $channels"if [ "$channels" = "" ]; then    echo "not found channels"    echo "build failure"    exit 1fichannels =$(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 cleanelse    sh ./gradlew -Dorg.gradle.java.home=/usr/alibaba/jdk1.7.0_25 cleanfi#循环编译出所有渠道的APKfor x in $channelsdo    packageApk $x    if [ $? -eq 0 ];then        echo "build $1 success"    else        echo "build $1 failure"        exit 1    fidoneecho "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();    } }

更多相关文章

  1. Ubuntu 12.04中Android(安卓)NDK r8d 编译 FFmpeg 0.11.1 并使用
  2. android通过HTTP协议上传文件至远程服务器
  3. android pagerView
  4. 从内存卡读取xml文件到内存,写入xml到sd卡
  5. Android(安卓)FileProvider的理解
  6. SharedPreferences存与取,
  7. 【Android】8.1 主题基本用法
  8. android 仿微信多图选择器(带预览、照相功能)
  9. Android(安卓)Launcher 如何去掉主菜单,所有应用摆在桌面,类似小米

随机推荐

  1. android RelativeLayout属性和使用, 实现
  2. Android开发之旅:活动与任务
  3. android中的照相机机拍照程序(含连续拍照)
  4. Android华容道——我的第二个Android程序
  5. (一)Android数据结构学习之链表
  6. 关于自己Android开发的感想,怎样深入系统
  7. 技术总结--android篇(二)--布局的优化
  8. Android Window类
  9. Android的线程和线程池
  10. Android(安卓)高级UI解密 (二) :Paint滤镜