天天看点

gradle常用场景总结多渠道打包自定义输出 APK 文件名Debug 和 Release 相同签名隐藏签名信息日志开关环境分离package 与 applicationIdBuild Variants实现方式

转自:gradle 常用场景

Gradle 作为一款灵活多变的构建插件,与 Android Studio 的结合,能够解决过去使用 Eclipse 开发 App 时所遇到的诸多问题。同时,基于 Groove 这样一款 DSL 语言的脚本特性,记住各种语法显然又是一件比较困难的事情。

事实上,Gradle 插件的常用使用场景并不是很多,也不需要死记硬背,或者完全学会 Groove 的所有使用方式。这里将 Android 开发中较为常用的 Gradle 使用场景总结出来,用于将来需要的时候有个参考。

多渠道打包

举一个最常见的多渠道使用场景,友盟统计,看看最基本的 Gradle 多渠道打包方式的用法。

修改 Manifest 文件中友盟统计的渠道名为引用变量(变量名自取):

<meta-data android:name="${UMENG_CHANNEL_VALUE}"
 android:value="Channel_ID" />
           

然后在 build.gradle 文件 productFlavors 配置项中添加渠道名,并统一设置到上面提到的变量名:

android {
    productFlavors {
        xiaomi {}
        yingyongbao {}
    }

    productFlavors.all {
        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }
}

执行打包 Task 的命令语句即可:

./gradle assembleRelease

或者有针对性的只打特定渠道包,如:

./gradle assembleXiaomiRelease
           

备注:在 Gradle projects 窗口中能够查看所有可执行的 Tasks 列表。这种原生态打包方式适合渠道名比较少的使用场景。当渠道多达数十个甚至上百个时,打包时间就会比较长,推荐其他打包方案,如:

多渠道打包方案

  • packer-ng-plugin
  • 美团点评技术团队:新一代开源Android渠道包生成工具Walle

自定义输出 APK 文件名

可以修改打包输出的 apk 文件名,添加时间戳、编译类型、版本信息等关键字,是 apk 文件更具识别性,如:

apply plugin: 'com.android.application'

def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

android {
    android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.outputFile = new File(output.outputFile.parent, rootProject.getName()
                    + "-" + buildType.name
                    + "-" + releaseTime()
                    + "-v" + defaultConfig.versionName
                    + "-" + defaultConfig.versionCode
                    + ".apk");
        }
    }
}

           

这里将项目名称添加到 apk 文件名中,也可以使用 applicationId 等,结果如下:

YFSample-release-2017-09-12-v1.0-1.apk
           

Debug 和 Release 相同签名

android {
    buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            buildConfigField "boolean", "DEBUG_MODE", "true"
            signingConfig signingConfigs.config
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            buildConfigField "boolean", "DEBUG_MODE", "false"
            signingConfig signingConfigs.config
        }
    }
}

           

这一步操作可以通过图形化界面操作,更方便一些。右键项目,点击 Open Module Settings,打开 Project Structure 窗口,在 Build Types 选项卡中设置。

隐藏签名信息

签名信息作为项目里最为私密的一部分内容,如果直接暴露在 build.gradle 文件中,显然不够安全。尤其是需要上传到 GitHub 等托管平台时。

比较妥当的做法是,在项目根目录下新建一个专门用来存储签名信息的文件,并且将其添加到 ignore 文件配置当中。具体操作过程如下:

第一步,新建一个 keystore.properties 文件:

ReleaseKeyPassword=sampleKeyPwd
ReleaseKeyAlias=sampleAlias
ReleaseStorePassword=sampleStorePwd
ReleaseStoreFile=../sample.jks
           

第二步,在 build.gradle 文件的最外层引入签名信息:

allprojects {
    afterEvaluate { project ->
        def propsFile = rootProject.file('keystore.properties')
        def configName = 'config'

        if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) {
            def props = new Properties()
            props.load(new FileInputStream(propsFile))
            android.signingConfigs[configName].storeFile = file(props['ReleaseStoreFile'])
            android.signingConfigs[configName].storePassword = props['ReleaseStorePassword']
            android.signingConfigs[configName].keyAlias = props['ReleaseKeyAlias']
            android.signingConfigs[configName].keyPassword = props['ReleaseKeyPassword']
        }
    }
}
           

如此即可。其中第二步还有一种缩减版的写法,也可以直接使用:

def keystorePropertiesFile = rootProject.file("keystore.properties");
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
    signingConfigs {
        config {
            storeFile file(keystoreProperties['ReleaseStoreFile'])
            storePassword keystoreProperties['ReleaseStorePassword']
            keyAlias keystoreProperties['ReleaseKeyAlias']
            keyPassword keystoreProperties['ReleaseKeyPassword']
        }
    }
}
           

当然,隐藏签名信息的方式还有很多。比如配置在本地环境变量中,或者 build.gradle 文件中,或者编译时从命令行中动态读取输入内容等,设置不同,读取方式有所不同。

日志开关

在 app/build.gradle 文件中分别定义 debug 和 release 不同编译模式的控制变量,变量名可自由更改:

buildTypes {
    debug {
        buildConfigField "boolean", "DEBUG_MODE", "true"
    }
    release {
        buildConfigField "boolean", "DEBUG_MODE", "false"
    }
}

           
重新编译后,自动生成的 BuildConfig 类中包含上面定义的 DEBUG_MODE 属性,即可使用。      

Log 日志开关更多解决方案,可参考:Android 中能够作为 Log 开关的一些操作以及安全性浅谈。

环境分离

开发中测试服务器和生产服务器的同时使用也是很常见的场景之一,如何在一台设备中同时安装配置测试环境的 debug 包和生产环境的 release 包,也是需要面对的问题。

对于Android App来讲,相同包名的apk在同一个设备上只能存在一个。所以我们无法做到在同一个设备上同时安装生产环境和测试环境的安装包,这对于日常的开发工作和测试人员的测试工作极不方便。总不能将整个工程复制一份,再通过修改包名的方式打包出另一个apk吧。所以在这种情况下,以往常见的做法就是在app中提供一个隐形的入口,供内部人员切换服务器地址,然后通过以下代码重启App:

private void restartApp(){
    Intent intent = getContext().getPackageManager().getLaunchIntentForPackage(getContext().getPackageName());
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    startActivity(intent);
}
           

显然,这种做法也只是一种缓兵之计,多少还是有些不尽人意。然而,值得庆幸地是,进入Android Studio时代后,Google开始使用引入Gradle构建系统,applicationId的出现使得环境分离的问题迎刃而解。

package 与 applicationId

在使用Eclipse开发Apk或旧版本的Gradle构建系统中,应用的包名由

AndroidManifest.xml

文件中package属性决定。同时,这个package还被用来定义命名被引用的资源类R文件。

但是在新的Android Gradle构建系统中,package属性的两大作用得到了解藕:applicationId作为应用的唯一标识符(包名),用于区分不同应用;package属性定义资源类R文件,用于引用。

applicationId存在于app/build.gradle文件中的defaultConfig配置下,新建项目时默认使用package属性值初始化,所以如果没有特殊的需求,一般我们不会在意和修改这个值:

defaultConfig {
        applicationId "cn.bankyellowriver.ningxia.unity"
        minSdkVersion 21
        targetSdkVersion 25
        multiDexEnabled true
        versionCode 2
        versionName "2.0.1"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
           

所以,要实现Apk的环境分离,也就是在同一设备上安装同一应用的不同版本,从本质上我们要修改applicationId的值,构建打包出不同包名的apk安装文件。Gradle构建系统提供了两种方式供开发人员修改applicationId的值,productFlavors和buildTypes,通过这两个方式我们可以轻松实现apk的打包定制,或者说Build Variants(构建变种)。

Build Variants

项目的productFlavors和buildTypes配置可以在app/build.gradle代码文件或者Project Structure上修改,作用是一样的。

productFlavors

项目可以通过定义多个不同的productFlavors来实现应用的不同定制版本,每一个Flavor与buildTypes配合产出对应的一种输出类型的apk文件,新建的项目初始化只有一个默认的Flavor:defaultConfig

注意:默认的defaultConfig为新建的productFlavors提供基本的配置,也就说,productFlavors的配置会覆盖defaultConfig中相同的属性,从而实现产品的不同定制版输出。对于环境分离,这里可以通过定义新的applicationId属性来实现。

buildTypes

默认情况下,项目的buildTypes包含debug和release两个构建版本,其中release版本的执行需要手动设置签名文件。对于环境分离,与productFlavors不同的是,buildTypes通过定义applicationIdSuffix来实现的,即添加后缀名

除了这些可配置的属性外,productFlavors和buildTypes都会通过各自的sourceSet来提供代码和资源,默认的路径为:src/flavorName和src/typeName。利用这个特性,我们可以实现不同定制版本的apk显示不同的应用名称和桌面图标,以便从设备上进行区分。

productFlavors和buildTypes配合产出各种格式为“flavorName + typeName”的Build Variants,以打包出不同版本的apk。当你没有自定义flavors,默认的defaultConfig也会与buildTypes形成对应的Build Variants,只是没有名字,所以显示为debug和release。比如这段配置:

gradle常用场景总结多渠道打包自定义输出 APK 文件名Debug 和 Release 相同签名隐藏签名信息日志开关环境分离package 与 applicationIdBuild Variants实现方式

我们配置了beta和production两种productFlavors,release和debug两种buildTypes。所以,对应的Build Variants就有四种,分别为:betaDebug、betaRelease、productionDebug和productRelease。可以在Build Variants窗口查看并选择对应的构建类型运行应用

注意:前面提到,buildTypes通过添加后缀的方式修改applicationId(包名)的,换句话说,就是在productFlavors的基础上修改包名的。所以,在上面这个例子中,betaRelease构建类型打包出的apk文件的包名是:com.yifeng.mdstudysamples.beta.debug。

实现方式

通过上面这些介绍,基本上大家能够知道在Android上如何实现app的环境分离了。我们可以选择使用productFlavors和buildTypes这两种方式在同一个设备上来安装同一个应用的不同版本。他们道理上是一样的,只是相比之下,使用buildTypes不用新建productFlavors,更为方便。这里我就buildTypes为例简单描述一下环境分离的实现。

1.对于一个默认productFlavors和buildTypes配置的项目,我们修改debug配置的applicationIdSuffix属性,设为”.debug”(名字可以随意设置),release版本不用变动。有了这一步,已经能够将debug版本和release的apk安装在同一个设备上了。还可以做到更好,比如修改debug版本的桌面图标、应用名称等,便于区分。

gradle常用场景总结多渠道打包自定义输出 APK 文件名Debug 和 Release 相同签名隐藏签名信息日志开关环境分离package 与 applicationIdBuild Variants实现方式

2.我们在src目录下新建一个debug目录,将main目录下的res目录复制一份到debug目录下,修改各个分辨率下的桌面Icon和strings.xml文件中的应用名称,加个debug标识。目录结构如图所示:

gradle常用场景总结多渠道打包自定义输出 APK 文件名Debug 和 Release 相同签名隐藏签名信息日志开关环境分离package 与 applicationIdBuild Variants实现方式

在构建打包时,debug目录下的res资源采用叠加的方式合并到main里面去,并替换相同的内容,而这个例子只需要修改桌面Icon和应用名称,所以这里我只复制了res目录下的相关文件,其他文件并未复制。

3.修改代码里的服务器接口地址,选择对应的Build Variants类型,运行即可。其实也可以在debug和main目录下的string.xml资源文件中定义服务器地址,然后在程序的入口处赋值给代码里的全局静态变量 。