gradle多版本打包设计

本文介绍了Android中使用Gradle进行多版本打包的方法,包括通过ProductFlavors和buildTypes实现不同特性的APK。详细讲解了build.gradle文件的配置,如apk特性设置、应用签名、混淆设置Proguard以及如何定制apk文件名。通过设置ProductFlavors的manifestPlaceholders实现不同应用名和图标,使用sourceSets管理不同特性代码,通过signingConfigs设置签名文件,并通过buildConfigField进行项目动态设置。
摘要由CSDN通过智能技术生成

一. 打包简介
android的打包系列有:ant打包(eclipse),gradle打包(Android-studio)和python打包(美团打包方式)等。这里主要介绍gradle打包,通过设置build.gradle文件的来实现同一套代码打包多种apk。打包多种apk的方式有多渠道打包和多版本apk打包,都是通过设置ProductFlavors来实现。总的来说,打包多种apk主要是通过设置ProductFlavors和buildTypes来实现。
二. 整体认知
2.1 原始的module的build.gradle文件介绍

创建项目后,module的build.gradle文件(这个build.gradle是局部的)的内容如下:
apply plugin: 'com.android.application'
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.3"
    defaultConfig {
        applicationId "civetphone.fsc.com.myapplication"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {  //依赖声明
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    testCompile 'junit:junit:4.12'
}
从上面我们知道,module的build.gradle文件主要分三部分:apply plugin ...,android{...}和dependencies{...}。apply plugin: 'com.android.application'这句中的apply是一个方法,给它传递了一个参数plugin,plugin的值是'com.android.application'。如果有多个参数,则用逗号隔开,例如compile name : 'volley',ext: 'aar'。apply plugin ... 的意思表示应用了android的gradle插件,提供了android编译,测试,打包等等的所有任务。在android{...}中我们可以设置编译android项目的参数,默认提供了defaultConfig和buildTypes这两项设置。defaultConfig其实是一个默认的ProductFlavors,在gradle配置文件中,defaultConfig是ProductFlavors中每个flavors的基础配置,只要我们重写appliactionId这个属性就会覆盖defaultConfig中相应属性的信息,从而使打包出来的两种apk的包名不一样(这里applicationId对应的就是AndroidManifest.xml文件中的package属性)。而另一个设置buildTypes是用来设置构建类型,默认的有debug和release。在dependencies{...}中,我们进行了了依赖的声明。
2.2 原始根目录的build.gradle文件的介绍
创建项目后的全局build.gradle的内容如下:
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.1'
    }
}
allprojects {
    repositories {
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}
从上面可以知道,全局的build.gradle文件默认也是分三个部分:buildscript{...},allprojects{...}和task clean{...}。buildscript{...}定义了android编译工具的类路径,说白了就是设置脚本的运行环境。buildscript中的声明是gradle脚本自身需要使用的资源,可以声明的资源包括依赖项,第三方插件,maven仓库地址等。它里面提供了两项配置repositories{...}和dependencies{...}。repositories{...}是代码仓库,平时添加的一些dependency就是从这里下载的,里面的jcenter()是著名的Maven仓库。仓库是一个进行集中存储东西的地方,放到这里可以理解为集中管理构件(jar包)的地方。仓库包含了绝大多数流行的开源Java构件,以及源码,作者信息,SCM,信息,许可证等。有了仓库可以解决侵占硬盘空间,依赖识别困难和依赖管理困难等问题。对于dependencies{...},我们在前面已经说了,他进行了了依赖的声明。allprojects块的repositories{...}用于多项目构建,为所有项目提供共同所需依赖包,而子项目可以配置自己repositories{...}以获取自己独需的依赖包。task clean{...}声明了一个任务,任务名叫做clean(也可以改为其它名字),任务类型是Delete(也可以是Copy),就是每当修改setting.gradle文件后点击同步,就会删除rootProject.buildDir下的文件。
三. 设置打包多种apk的build.gradle的分析
设置打包多种apk的build.gradle文件的主要内容如下:
android {   
    compileSdkVersion 24
    buildToolsVersion "24.0.3"
    defaultConfig { //默认配置
        applicationId "com.fsc.civetphone"
        minSdkVersion 15 
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        multiDexEnabled true 
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    signingConfigs {  //设置应用签名
        debug {
            storeFile file("debug.keystore")
        }
        civet_release   
        pajama_release
    }
    dexOptions {    //将javaMaxHeapSize的值扩大到4g,这样可以优化gradle的编译速度
        javaMaxHeapSize "4g"
    }
    lintOptions {   //lint是Android提供的一个静态代码分析工具
        checkReleaseBuilds false
        abortOnError false
    }
    sourceSets {    //实现不同的apk对于不同的特性
        civet {
            manifest.srcFile 'src/civet/AndroidManifest.xml'
            java.srcDirs = ['src/civet/java']
            res.srcDirs = ['src/civet/res']
        }
        pajama {
            manifest.srcFile 'src/pajama/AndroidManifest.xml'
            java.srcDirs = ['src/pajama/java']
            res.srcDirs = ['src/pajama/res']
        }
    }
    productFlavors {    //配置多种的apk
        civet {
            applicationId "com.fsc.civetphone"
            manifestPlaceholders = [app_name: "multi_versionapk.civet", icon: "@mipmap/ic_launcher"]
        }
        pajama {
            applicationId "com.fsc.civetphone.pajama" 
            manifestPlaceholders = [app_name: "multi_versionapk.pajama", icon: "@mipmap/pajama"]
        }
    }
    buildTypes {    //构建类型
        release {
            buildConfigField "boolean", "FUNCTION_LOG_DEBUG", "false"    
            applicationIdSuffix ".release"
            zipAlignEnabled true    
            shrinkResources true   
            minifyEnabled true      
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.civet_release
        }
        debug {
            applicationIdSuffix ".debug"
            signingConfig signingConfigs.pajama_release
        }
    }
    applicationVariants.all { variant ->    //定制生成apk的文件名
        variant.outputs.each { output ->
            def file = output.outputFile
            def apkName = variant.productFlavors[0].name + '_' + defaultConfig.versionName + '_' + variant.buildType.name + '.apk'
            output.outputFile = new File(file.parent, apkName)
            if (output.zipAlign != null) {
                output.zipAlign.doLast {
                    output.zipAlign.inputFile.delete()
                }
            }
        }
    }
}
def ksFile = rootProject.file('keystore.properties')//动态加载签名文件
def props = new Properties();
if (ksFile.canRead()) {
    props.load(new FileInputStream(ksFile))
    if (props != null) {
        android.signingConfigs.civet_release.storeFile file(props['KEY_STORE_FILE'])
        android.signingConfigs.civet_release.storePassword props['KEY_STORE_PASSWORD']
        android.signingConfigs.civet_release.keyAlias props['KEY_ALIAS']
        android.signingConfigs.civet_release.keyPassword props['KEY_PASSWORD']
        android.signingConfigs.pajama_release.storeFile file(props['KEY_STORE_FILE'])
        android.signingConfigs.pajama_release.storePassword props['KEY_STORE_PASSWORD']
        android.signingConfigs.pajama_release.keyAlias props['KEY_ALIAS']
        android.signingConfigs.pajama_release.keyPassword props['KEY_PASSWORD']
    } else {
        println 'some entries in \'keystore.properties\' not found!'
    }
} else {
    println '\'keystore.properties\' not found!'
}
从上面可以知道,打包多种apk的build.gradle文件的内容有点杂,但是我们可以根据它们各自的功能将它们分成apk特性设置,应用签名的设置,混淆文件设置之Proguard和其他设置这4部分,下面让我们来一步步分析这个打包多种apk的build.gradle文件。
四. apk特性设置之 buildTypes + productFlavors + sourceset
4.1  构建类型--buildTypes

buildTypes称为构建类型,默认有debug和release两个版本,buildType默认共享一个defaultConfig。
4.2  定制产品--productFlavors
productFlavors称为定制产品,默认是匿名的,不同的flavor可以对应不同的xxxConfig,由此就可以拥有不同的包名。productFlavors实现在一个工程中开发不同特性的apk,以及更方便的依赖管理,这里设置civet和pajama这两种flavors。我们还可以在flavor中使用占位符manifestPlaceholders属性来设置不同的应用名和icon等,同时AndroidManifest.xml中也做对应的配置(这里是设置占位符代替它的应用名和icon)。
4.3  特性资源--sourceset
使用sourceset实现不同apk对应不同的特性的源代码。每个flavor都可以对应一个sourceset,我们可以在src/main同级的目录下新建另外两个目录civet和pajama,并放各自特性相关的代码。然后在sourceSets在设置代码的路径,如上面的设置。这样在build的时候这些代码会与main下面的代码按照一定的规则进行合并,最终组成当前variant对应的完整代码。
将它们三者结合起来使用可以实现同一套代码打包多种有独自特性的apk。在这里,我们buildType设置了debug和release,productFlavors设置了civet和pajama,这样buildVariant的类型就有4种,因为buildVariant是productFlavors + buildTypes的结合,这样两两结合就可以产生4种apk。另外,在buildVariant面板中查看切换到具体buildVariant时,Android-studio视图下对应的代码就会按具体的buildVariant类型去组合,这样显示出的代码就是你当前variant所要build的代码。
五. 应用签名的设置
通过上面的设置,我们已经能在Android-studio的buildVariant选择4种不同的apk了。接下来我们来设置应用签名,这里分为签名文件的设置和应用签名的引用。
5.1  签名文件的设置
应用签名的设置我们是在signingConfigs中进行的,要设置应用签名,我们先要设置签名文件。这里我们使用android-studio的build----->Generate Signed APK,然后设置生成就可以了。在signingConfigs中我们可以使用一下方式加载签名文件:
release {
    storePassword '123456'
    keyAlias '123456'
    keyPassword '123456'
    storeFile file('keystore/DebugFSC.jks')
}
但是这样在gradle文件中写密码不安全,所以我们使用动态加载来加载签名文件。主要的设置如下:
def ksFile = rootProject.file('keystore.properties')
def props = new Properties();
if (ksFile.canRead()) {
    props.load(new FileInputStream(ksFile))
    if (props != null) {
        android.signingConfigs.civet_release.storeFile file(props['KEY_STORE_FILE'])
        android.signingConfigs.civet_release.storePassword props['KEY_STORE_PASSWORD']
        android.signingConfigs.civet_release.keyAlias props['KEY_ALIAS']
        android.signingConfigs.civet_release.keyPassword props['KEY_PASSWORD']
        android.signingConfigs.pajama_release.storeFile file(props['KEY_STORE_FILE'])
        android.signingConfigs.pajama_release.storePassword props['KEY_STORE_PASSWORD']
        android.signingConfigs.pajama_release.keyAlias props['KEY_ALIAS']
        android.signingConfigs.pajama_release.keyPassword props['KEY_PASSWORD']
    } else {
        println 'some entries in \'keystore.properties\' not found!'
    }
} else {
    println '\'keystore.properties\' not found!'
}
使用rootProject.file获取记录签名文件相关信息的keystore.properties属性文件,并使用canRead()和load(...)方法进行读取和加载keystore.properties属性文件中的数据,然后是使用android.signingConfigs获取我们设置的相应数据即可。这样我们就直接可以通过android-studio的build----->Build APK来生成apk了,也不用担心密码泄漏。
5.2  应用签名的引用
应用签名的引用是在buildTypes中具体的构建类型中,我们可以通过signingConfig signingConfigs.xxx来选择具体的应用签名方式,在release构建中我们选择了civet_release这种方式,而在debug构建中,我们选择了pajama_release。它们在signingConfigs进行了声明,并使用动态加载进行设置。
六. 混淆文件设置之Proguard
通过上面的设置。我们就可以build出4种类型的apk,但是这样apk被反编译后容易被破解,所以我们要对apk就行混淆设置。混淆我们使用Proguard技术,接下来我们就来介绍Proguard技术并使用它进行相关的混淆设置。
6.1 Proguard技术的介绍
Proguard工具是用于压缩,优化,混淆我们的代码,主作用是可以移除代码中的无用类,字段,方法和属性。同时可以混淆(类,字段,方法,属性的)命名,最终结果可以使我们的apk文件体积更小,也会让我们的apk更加难以被他人逆向工程。ProGuard技术的功能概括为以下4项:
6.1.1 压缩(shrinks) :检查并移除代码中无用的类,字段,方法,属性。
6.1.2 优化(optimizes):对字节码进行优化,移除无用的指令。
6.1.3 混淆(obfusates) : a,b,c,d等简短而无意义的名称对类,字段和方法进行重名,这样即使代码被逆向工程,对方也比较难以读懂。
6.2.4 预检测(Preveirfy) : 在java平台上对处理后的代码进行再次检测。
6.2  Proguard混淆技术的使用
ProGuard混淆技术是集成到Android构建系统,所以我们不需要手动调用它,系统会在环境需要时自动去调用,因此我们要做的唯一一件事就是写好ProGuard混淆文件。ProGuard混淆技术只有当我们构建应用程序并准备发布应用时才需要使用,所以我们在debug模式下没有必要进行代码混淆。Android-studio在build.gradle文件中使用proguard文件。在构建类型(buildTypes)中通过minifyEnable属性设置为true来启动混淆优化功能,然后使用proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'来设置混淆,其中proguadFiles这部分有两段,前一部分'proguard-android.txt'代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,免去了我们很多事,这个文件的目录在<sdk目录>/tools/produard/proguard-android.txt,后一部分'proguard-rules.pro'是我们项目里的自定义的混淆文件,目录就在app/proguard-rules.pro。
6.3  自定义混淆文件(proguard-rules.pro)
在这个文件里,我们可以声明第三方依赖的一些混淆规则和一些自己的混淆规则。主要设置有以下这些:
6.3.1 基本设置(如代码的压缩级别,大小写混合,混淆记录日志等等)
6.3.2 指明不需要混淆的相关的信息(如native方法,v4包等等)
6.3.3 对于我们特定的app相关的信息:保留module中的实体类和成员不被混淆,保留内部类不被混淆,WebView相关信息不被混淆,JavaScript相关信息不被混淆,反射的处理。
6.3.4 针对第三方jar包的解决方案
自定义混淆文件要注意的点主要有以上这些,具体的设置可以参考<<Android多渠道打包之混淆文件ProGuard技术详解 - 特别篇>>这篇博客。
七. 其他设置
至此,我们的多种apk打包就算完成的差不多了,但是为了我们的一些特殊需要和方便操作,我们还可以进行如下的操作:
7.1  定制生成apk文件名
我们在Android{...}中进行如下的设置:
applicationVariants.all { variant ->    //定制生成的apk文件名
    variant.outputs.each { output ->
        def file = output.outputFile
        def apkName = variant.productFlavors[0].name + '_' + defaultConfig.versionName + '_' + variant.buildType.name + '.apk'
        output.outputFile = new File(file.parent, apkName)
        if (output.zipAlign != null) {
            output.zipAlign.doLast {
                output.zipAlign.inputFile.delete()
            }
        }
    }
}
这样就可以对我们打包的4种apk进行文件名的设置,这样我们使用它们具体的productFlavors,versionName和buildType来定制它们的名字。
7.2  为各种apk设置包名
在前面,我们已经通过applicationId来设置apk的包名。但是,在相同的flavor中,debug和release方式生成的apk包名是一样的,所以依次运行这4种variant后,我们发现同一台模拟器只能同时安装两种apk。此时,我们可以在buildType中的release和debug中设置applicationIdSuffix来使它们的包名不一样。applicationIdSuffix字面意思是applicationId的后缀。默认创建项目后,applicationId和packageName两者相同。如果需要根据不同的需要构建不同的版本的apk,这时我们可以通过设置applicationIdSuffix来做到。
7.3  利用buildConfigField进行项目的动态设置
我们打包编译项目时,可以在build type设置一些参数信息,直接可以作用于项目。从而在项目中,动态根据build.gradle中设置的信息,进行一些代码层面的逻辑处理。比如在debug type下正常输出日志信息,但是在release type下,屏蔽日志输出,那么就可以通过buildConfigField来进行动态设置。
例如,有时我们可能希望使用buildConfigField来动态设置具体apk的名字,比如civet运行配置设置ConfigEnvType OFFICIAL=1; ITE=2; INTERNAL=3; pajama OFFICIAL=11; ITE=12; 其他数字默认为ITE环境。此时我们可以先在productFlavors进行如下的设置:
productFlavors {    //app变种
    civet {
        applicationId "com.fsc.civetphone"
        //buildConfigField("int", "SERVER", "1")
        //buildConfigField("int", "SERVER", "2")
        buildConfigField("int", "SERVER", "3")
    }
    pajama {
        applicationId "com.fsc.civetphone.pajama" 
        //buildConfigField("int", "SERVER", "11")
        buildConfigField("int", "SERVER", "12")
    }
}
从上面可以看出,我们设置了一个int类型常量“SERVER”,然后在civet给根据需要给它设置值为1,2,3中的其中一种,在pajama中给它设置11或12中的一种。
然后我们将定制生成apk文件名的gradle设置改为如下:
applicationVariants.all { variant ->    //定制生成的apk文件名
    variant.outputs.each { output ->
        def file = output.outputFile
        def apkName = variant.productFlavors[0].name + '_' + defaultConfig.versionName + '_' + 'ITE' + '.apk'
        if (variant.productFlavors[0].buildConfigFields.get("SERVER").getValue().equals("1")){
            apkName = variant.productFlavors[0].name + '_' + defaultConfig.versionName + '_' + 'OFFICAL' + '.apk'
        }
        if (variant.productFlavors[0].buildConfigFields.get("SERVER").getValue().equals("3"))
            apkName = variant.productFlavors[0].name + '_' + defaultConfig.versionName + '_' + 'INTERNAL' + '.apk'
        }
        if (variant.productFlavors[0].buildConfigFields.get("SERVER").getValue().equals("11")){
            apkName = variant.productFlavors[0].name + '_' + defaultConfig.versionName + '_' + 'OFFICAL' + '.apk'
        }
        output.outputFile = new File(file.parent, apkName)
        if (output.zipAlign != null) {
            output.zipAlign.doLast {
                output.zipAlign.inputFile.delete()
            }
        }
    }
}
从上面我们知道,我们为apk设置了一个默认的名字,当“SERVER”设置为其他值,我们默认使用这个默认的apk名字。另外,当选择civet时,“SERVER”设置为2,或者选择pajama时,“SERVER”设置为12时,apk名字最后默认拼接上“ITE”就可以了,也就是使用这个默认名字。然后我们使用variant.productFlavors[0].buildConfigFields.get("SERVER").getValue()去取得"SERVER"常量对应的值,并进行判断,最后根据我们的需要拼接apk的名字。如上面的设置,我们设置civet的"SERVER"值为1时,apk名字最后拼接上“OFFICAL”,选择设置为3的时候,apk名字最后拼接上“INTERNAL”,选择设置其他数字的时候默认拼接上“ITE”。而pajama的"SERVER"值为11,apk名字最后拼接上“OFFICAL”,选择设置其他数字的时候默认拼接上“ITE”。当我们在android-studio切换到civet或者pajama的时候,productFlavors会选择对应的flavor进行build,此时其他的flavor无效 ,这样我们就可以使用variant.productFlavors[0]取得我们选择的flavor并build出具体的apk。
7.4  使用lint进行代码审查
用gradle build命令时,经常由于lint错误终止,而这些错误有经常是第三方库中的,我们可以跳过这些错误,继续编译。这时,我们可以使用lintOptions { abortOnError false }来实现这个功能。
7.5  gradle的一些优化
在build.gradle文件里,我们可以通过扩大dexOptions {  javaMaxHeapSize "4g" }里javaMaxHeapSize的值来优化gradle的编译速度。另外,可以通过在buildType的release中配置zipAlignEnabled true 和 shrinkResources true来设置进行release方式构建的时候启动zipAlign压缩和移除无用的resource文件,这样编译出来的apk会相对比较小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值