android gradle自动化打包方案完整总结

简介

3ac8c26a-0fa1-4bc2-b12a-d9c78d7c0a6e.png

以前知道自动化打包,最近分享点东西,根据官方和一些参考文档,做一下简单的总结,简单做了张图,知识点文章都会介绍到,做了个demo,对各功能做个测试。

build生成apk方式

1.android sutidio 菜单栏,build-》generate signed apk 随后可以选择编译方式和产品风味
2.在android根目录下命令行

  • ./gradlew assemble{BuildType} 对应编译方式所有productflavor都会输出
  • ./gradlew assemble{productFlavor}{BuildType} 指定构建体输出文件
  • ./gradlew assemble全部构建变体都会输出。

PS:打产品包用Release方式

  • ./gradlew assembleRelease 或者简写./gradlew aR打包所有产品风味的包
  • ./gradlew assemblerelease_normalRelease指定产生release_normal产品风味的包

install方式

1.android sutidio 直接菜单栏的Run绿色按钮。左下脚菜单Build Variants,可以选择不同的构建变体
2.在android目录下运行命令
./gradlew install{productFlavor}{buildType}
注意:这种方式如果build自增体现在输出的apk文件名上,可能会报找不到apk文件的错误。

签名不在版本控制中

正常情况下会在build.gradle 下面配置签名文件的位置和密码,但是签名文件和密码会在版本控制中,不安全,至少密码在版本控制中是不安全的。
推荐两种方式
1.自定义存储签名密码文件,并自定义函数读取签名密码和签名文件位置。
2.在本地~/.gradle/gradle.properties全局文件中配置签名密码和签名文件位置

自定义读取签名密码文件

单独建立一个签名密码的文件,在工程目录下新建signingfiles文件夹,在里面存储签名文件和签名密码文件

  • keystore.properties
  • guanmai-release-key.keystore

签名密码文件

STORE_FILE=../signingfiles/guanmai-release-key.keystore
KEY_ALIAS=guanmai-key-alias
STORE_PASSWORD=guanmai
KEY_PASSWORD=guanmai

在版本控制.gitignore中将密码文件或者签名文件忽略掉,在build.gradle中读取签名密码文件。
在android{}函数外部增加设置签名的函数

//从../signingfiles/keystore.properties下读取签名文件的位置和密码
//如果没有相关文件或者参数不对将抛出异常
def setSigningProperties(){
    def propFile = file('../signingfiles/keystore.properties')
    if (propFile.canRead()){
        def Properties props = new Properties()
        props.load(new FileInputStream(propFile))
        if (props!=null && 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 {
            println 'signing.properties found but some entries are missing'
            throw new Exception("signing.properties found but some entries are missing")
            //android.buildTypes.release.signingConfig = null
        }
    }else {
        println 'signing.properties not found'
        throw new Exception("signing.properties not found:" + propFile.absolutePath)
        //android.buildTypes.release.signingConfig = null
    }
}

signingconfigs读取并设置密码和签名文件路径

    signingConfigs {
        release {
            setSigningProperties()
        }
    }

本地配置签名密码

rn官网曾提到的方法http://reactnative.cn/docs/0.42/signed-apk-android.html#content
签名配置文件放在全局的本地~/.gradle/gradle.properties中,签名文件也放到自己的本地~/.gradle/release-key.keystore

MYAPP_RELEASE_STORE_FILE=~/.gradle/release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=key-alias
MYAPP_RELEASE_STORE_PASSWORD=pass
MYAPP_RELEASE_KEY_PASSWORD=pass

build.gradle中配置签名

    signingConfigs {
        release {
            storeFile file(MYAPP_RELEASE_STORE_FILE)
            storePassword MYAPP_RELEASE_STORE_PASSWORD
            keyAlias MYAPP_RELEASE_KEY_ALIAS
            keyPassword MYAPP_RELEASE_KEY_PASSWORD
        }
    }

这样签名文件与与密码都不在版本控制中,尤其是工作中正式app签名用的都是一个,会很方便。

查看签名文件

//查看签名文件的属性
keytool -list -keystore 签名文件

//查看 apk 的签名,需要提前解压 apk ,获取 CERT.RSA(位于解压目录下 /META-INF 下)
//以下命令行是在 apk 解压目录下执行
keytool -printcert -file META-INF/CERT.RSA

版本名命名VersionName

VersionName是一个字符串,例如 "1.2.0",这个是给人看的版本名,系统并不关心这个值,但是合理的版本名,对后期的维护和 bug 修复也非常重要。

参考
版本号规则
https://developer.android.com/studio/publish/versioning.html
主流app版本号
http://www.ifeegoo.com/recommended-mobile-application-version-name-management-specification.html

版本versionName主要采用形式
v1.0.0.buildnum
1.版本号
<major>.<minor>.<point>

  • major是主版本号,当项目在进行了重大修改或局部修正累积较多,而导致项目整体发生全局变化时,主版本号加 1;
  • minor是次版本号,一般在软件有新功能时增长
  • point是修正版本号,一般在软件有主要的问题修复后增长

2.buidnum为build次数
可以采用build次数记录自增的方式,参考下面的VersionCode
在文件gradle.properties文件中build.num变量

如何赋值versionCode

版本号,是一个大于 0 的整数,相当于 Build Number,随着版本的更新,这个必须是递增的。大的版本号,覆盖更新小的版本号。那么除了手动增加外,还有其他自增的方案

方案

build次数自增

1.要实现这个功能,首先得有个变量记录次数,在工程根目录下的gradle.properties增加build.num属性

build.num=1

2.需要读取个属性,定义读取这个build.num的函数

def getBuildNum() {
    def buildFile = file('../gradle.properties')

    if (buildFile.exists()) {
        def Properties buildPop = new Properties()
        buildPop.load(new FileInputStream(buildFile))
        def buildNum = buildPop['build_num'].toInteger()
        println('Current version code is ' + buildNum.toString())
        return buildNum
    } else {
        throw new GradleException("Could not find " + buildFile.absolutePath)
    }
}

3.此外我们还得能够赋值这个属性,定义自增函数

//加一标志,确保一次编译不管生成几个apk,都只增加一
def add_flage = false
//非'Debug'编译方式,自增
def addBuildNum() {
    def runTasks = gradle.startParameter.taskNames.toString()
    println("runtasks!: " + runTasks)
    if ((runTasks.contains('Debug'))) {
        return
    }

    def File buildFile = file('../gradle.properties')
    if (buildFile.exists()) {
        def Properties versionProps = new Properties()
        versionProps.load(new FileInputStream(buildFile))
        def buildNum = versionProps['build_num'].toInteger()
        buildNum++
        versionProps['build_num'] = buildNum.toString()
        versionProps.store(buildFile.newWriter(), null)
        println('Updated build number to ' + buildNum.toString())
    } else {
        throw new GradleException("Could not find " + buildFile.absolutePath)
    }
}

4.在合适条件下build先自增,然后在再给versionNameVesionCode赋值

        //所有编译方式BuildType都会遍历flavors
        productFlavors.all { flavor ->
            //buid自增,一次编译只增一次
            if (!add_flage ){
                addBuildNum()
                add_flage = true;
            }

            //build自增后 重新赋值 versionname;versionCode
            versionName "v1.2.1.${getBuildNum()}"
            versionCode getBuildNum()
        }

git commit 次数自增

参考http://www.race604.com/android-auto-version/

发布时间

通过发布时间函数

def releaseTime() {
    return new Date().format("yyMMddhhmm", TimeZone.getTimeZone("UTC"))
}

将返回的字符串转换为int值,作为versionCode,versionCode为整数有长度限制,忘记了多少,自行查看官网。

多应用安装

https://developer.android.com/studio/build/build-variants.html
实现多应用的安装应该有的步骤
1.首先要让编译有不同产品的编译,定义不同产品风味productFlavors
2.有了不同的产品编译,生成了apk,要定义不同的文件名,生成不同的apk。
3.生成不同名子的apk,如果ApplicationId一样的话,也不能同时安装在一台机器上,要在productFlavors定义不同的ApplicationId

产品风味productFlavors,生成多个apk

  • env 发布测试环境
  • release_normal 正式发布正常升级
  • company_{公司关键词} 不同公司版本
  • 渠道 不同渠道版本

在构建的变体里 可以设置不同的build参数,如applicationId、渠道号、versionName等
注意:android studio 正常运行可以选择构建方式(debug签名文件跟release不一样),可以在左下角的Build Variants 菜单中选择构建方式,注意点看输出文件

输出文件

输出的文件名可以根据的自己需要自定义例如
${profix}_${projectName}_${variant.versionName}
在 applicationVariants.all定义输出文件名

    //输出文件参数设置
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            //只定义release编译方式的apk文件名
            if (outputFile != null && outputFile.name.endsWith('.apk') && 'release'.equals(variant.buildType.name)) {

                def flavor =  variant.productFlavors[0].name;

                //不同的falvor,定义不同的输出的apk文件名前缀
                def profix
                if (flavor.equals("release_normal")){
                    profix = "gm"
                } else if (flavor.equals('env')){
                    profix = "test_gm"
                }else if (flavor.contains("company_")){
                    profix = flavor.replace("company_","");
                }else {
                    profix = "gm_" + flavor
                }

                //定义输出apk文件名
                def fileName = "${profix}_${projectName}_${variant.versionName}.apk".toLowerCase()
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }

一机多应用安装

根据不同的产品风味ProductFlavors,定义不同applicationId

//以company开头 加入不同公司版
//在这里我可以更改包名 app在手机中可以和其他flavor编译的app共存
company_xnv{
    applicationId "com.versiontest.xnv"
    manifestPlaceholders = [PGYER_APPID_VALUE: "other PGYER_APPID_VALUE"]
}

不同编译拥有不同资源文件

在 main 的同级目录下创建以productFlavors或者buildTpye名命名的文件夹,然后创建资源文件,然后打包的时候 gradle 就会自己替换或者合并资源。
例如, App 的默认 string.xml 路径为main\res\value\string.xml,那么 相应的构建变体路径 env\res\value\string.xml ,打包 env 构建变体的时候会自动替换当中的字符串。
在android studio 中右键选择new->android resources directory 弹出对话框 ==source set==选项,选择相应的构建变体,以及相应的资源文件类型,确定就会自动新建相应的目录了。

main 目录中:

<resources>
    <string name="app_name">GuanmaiDemoGradle</string>
</resources>

env 目录中

<resources>
    <string name="app_name">DemoGradle</string>
</resources>

那么用env构建apk,apk的名字就会变成DemoGradle

代码中不同编译方式得到不同变量

在实际项目,我们可能需要通过不同的构建变体,得到一些不同的变量,产生不同的业务逻辑,例如想打一个测试环境包,就想得到一个环境变量,赋值不同url前缀。
大约三种方式,
1.resValue
2.buildConfigField,
3.<meta data>方式,也是经常看到渠道方式,除了渠道外,很多更改第三方appId等,都可参考。

“resValue”string.xml中增加变量

1.在资源文件中新增变量。在你的 gradle 文件 buildTypes 或者 productFlavors 下面,如 release 体内写上类似:
resValue "string", "app_name_build", "Test打包Rn"
就相当于在资源文件string.xml多了
<string name="app_name_build">Test打包Rn</string>
代码中就可以读取string.xml中的app_name_build

注意: 只能在string.xml中新增加字符对,不能改变原有的字符对,或者说不能与原有的字符对重复,会报冲突错误。

“buildConfigField”BuildConfig中新增变量

在BuildConfig中新增变量。在你的 gradle 文件 buildTypes 或者 productFlavors 下赋值新的变量, 例如赋值字符串ENV变量:

buildConfigField "String", "ENV", "\"development\""

在代码中便可以引用BuildConfig.ENV

<meta data>(渠道)

渠道一般都是通过友盟,以友盟为例来说明渠道方式。
通过在 AndroidManifest.xml 文件中 application 标签下指定<mate-data> 设置占位符来实现动态替换属性值。

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

在build.gradle中,是渠道的构建变体 ==UMENG_CHANNEL== 为其名字,其他为default

if (name.contains("debug") || name.contains("release"))
    flavor.manifestPlaceholders = [UMENG_CHANNEL: "default"]
else
    flavor.manifestPlaceholders = [UMENG_CHANNEL: name]

读取application中的<meta data>

  private String getMetaValue(String metaName){
    ApplicationInfo appInfo = null;
    try {
      appInfo = this.getReactApplicationContext().getPackageManager()
              .getApplicationInfo(this.getReactApplicationContext().getPackageName(),
                      PackageManager.GET_META_DATA);
    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
    }
    String msg=appInfo.metaData.getString(metaName);
    return msg;
  }

如果渠道很多,就参考下美团和其他github上的多渠道打包
http://www.jianshu.com/p/76ab2ff11229

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 141,623评论 20 608
  • 1.介绍 如果你正在查阅build.gradle文件的所有可选项,请点击这里进行查阅:DSL参考 1.1新构建系统...
    Chuckiefan阅读 8,157评论 8 69
  • 这一章主要针对项目中可以用到的一些实用功能来介绍Android Gradle,比如如何隐藏我们的证书文件,降低风险...
    leil_阅读 4,806评论 3 24
  • 最近在公司项目中需要利用同一套代码打包出具有不同皮肤和有一些功能差异的两个apk包,于是学习了一下Android ...
    Alexyz123阅读 8,714评论 0 12
  • 阿狸,我最爱的东西!很有心的他给我买了好多关于阿狸的东西,漫画书,涂鸦本,玩偶,都是阿狸!可能在他的心里,我...
    全世界我想遇见你阅读 27评论 0 0