<APK打包>Android打包签名总结

题外话

一个人做项目的开发和维护,个人觉得还是挺不错的,很自由。当然,也会遇到各种各样的问题,都要自己解决处理,毕竟一个人,但这也是提升个人技术的最快途径。

最近项目上线相对比较频繁,一个基础产品(主要产品,称之为base吧),还有一个作为马甲产品(称之为mP1吧,鬼知道以后会不会有更多马甲),两个产品每两周就要各上一个版本,每个都要打好几十个包,这在之前,是很痛苦的。试想一个场景:

快要下班了,领导问马甲产品的功能都做得差不多了吧,好,那就今天马甲产品上线。那就开始打包吧,需要打60个包呀,好,一切都检查好了,觉得没问题了,那就快马加鞭的开始打包吧,在命令行输入gradlew assembleRelease ,就开始了漫长的等待,要下班啊,着急啊,死死地盯着命令行那一动不动的百分号进度···

终于到了99%,结果无意间发现项目的版本号没有+1,我去,想死的心都有了~~~

后续情景可以自行脑补了,于是,痛定思痛,决定第二天来了,一定要找到一种省时省力的打包方式。这就是下文的由来。

打包进化的三个版本

注意:考虑到多马甲产品,工程里的Java文件的packageName统一,只修改应用的applicationId即可

版本一:复制项目,单独打包

1、将基础工程复制一份,作为马甲项目,只看app/build.gradle 文件:

apply plugin: 'com.android.application'

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

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.2'
    useLibrary 'org.apache.http.legacy'

    defaultConfig {
        applicationId "com.ylzt.mP1"
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 10
        versionName "1.0"

        ndk {
            //选择要添加的对应cpu类型的.so库。
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
            // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
        }

       multiDexEnabled true
    }

    lintOptions {
        //checkReleaseBuilds false
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
//            abortOnError false
        disable "MissingTranslation"
    }
    //打包渠道
    productFlavors {
        "0010001" {}
        "0010002" {}
        "0010003" {}
        "0010004" {}
        "0010005" {}
        "0010006" {}
        "0010007" {}
        "0010008" {}
        "0010009" {}
        "0010010" {}
        "0010011" {}
        "0010012" {}
        "0010013" {}
        "0010014" {}
        "0010015" {}
        "0010016" {}
        "0010017" {}
        "0010018" {}
        "0010019" {}
        "0010020" {}
        "0010021" {}
        "0010022" {}
        "0010023" {}
        "0010024" {}
        "0010025" {}
        "0010026" {}
        "0010027" {}
        "0010028" {}
        "0010029" {}
        "0010030" {}
        "0010031" {}
        "0010032" {}
        "0010033" {}
        "0010034" {}
        "0010035" {}
        "0010036" {}
        "0010037" {}
        "0010038" {}
        "0010039" {}
        "0010040" {}
        "0010041" {}
        "0010042" {}
        "0010043" {}
        "0010044" {}
        "0010045" {}
        "0010046" {}
        "0010047" {}
        "0010048" {}
        "0010049" {}
        "0010050" {}
        "0010051" {}
        "0010052" {}
        "0010053" {}
        "0010054" {}
        "0010055" {}
        "0010056" {}
        "0010057" {}
        "0010058" {}
        "0010059" {}
        "0010060" {}
    }

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

    signingConfigs {
        debug {
            // No debug config
            storeFile file("C:/Users/dev-android/.android/debug.keystore")

            storePassword "xxxxxx"
            keyAlias "xxx"
            keyPassword "xxxxxx"

        }

        release {
            storeFile file("D:/keystore/xxx.keystore")

            storePassword "xxxxxx"
            keyAlias "xxx"
            keyPassword "xxxxxx"
        }
    }

    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles 'proguard-rules.pro'
        }

        release {
            // 不显示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"

            // 是否进行混淆
            minifyEnabled true
            // 混淆文件的位置
            proguardFiles 'proguard-rules.pro'
            signingConfig signingConfigs.release

            shrinkResources true  // 移除无用的resource文件
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.android.support:recyclerview-v7:25.1.0'

    /*依赖module*/
    compile project(':..:LibModules/:core')

    //图片上传
    compile files('libs/httpmime-4.1.1.jar')
    //unzip
    compile files('libs/apache-ant-zip.jar')
    //wx
    compile files('libs/libammsdk.jar')

    ······
}

这里的所有渠道都用编号表示,第二位1就是代表的第一个马甲产品mP1,基础产品的第二位是0(例如:0000001),这是为了区分各个产品,通过不同编号来对应各个渠道。

这里和基础产品不同的有两处:
第一:applicationId "com.ylzt.mP1" ,基础的为applicationId "com.ylzt"
第二处:就是上面所说的,渠道编号的第二位不同

其他不同的,就是代码里的逻辑了,或者页面UI之类的,这个就不说了。

2、修改完app/build.gradle 之后,直接在window命令行中输入命令即可:

Y:\>cd Y:\WorkPlaces\AndroidStudioWP\ApplicationDemos\app
Y:\WorkPlaces\AndroidStudioWP\ApplicationDemos\app>gradlew assembleRelease
命令行截图

缺点:
耗时——渠道越多,所用时间越长
繁琐——多一个马甲,就需要在多复制一份工程
笨拙——一处修改,所有的马甲都需要修改
......


版本二:整合项目,变量控制打包

思路:通过gradle的productFlavors属性创建多个不同版本的App

要点:

1、packageName和applicationId的区别:
packageName即为包名,资源文件(R文件和四大组件等)的路径
applicationId作为应用的唯一标识
[参考官方文档ApplicationId versus PackageName(需要翻墙)]

具体步骤:

1、创建版本文件夹:
在src目录下建立相应的版本文件夹,需要多少个版本就创建多少个文件夹,如图所示:

多版本目录图示
文件内容详情图示

注:在多版本目录图示中
红框标注的文件是唯一的
蓝绿色框标注的文件表示可以同时存在各个版本之中,但不能出现在main目录中
蓝紫色框标注的文件都可以存在个目录中

这就涉及到了合并规则:
a、图片、音频、 XML 类型的 Drawable 等资源文件,将会进行文件级的覆盖
b、字符串、颜色值、整型等资源以及 AndroidManifest.xml ,将会进行元素级的覆盖
c、代码资源,同一个类, buildTypes 、 productFlavors 、 main 中只能存在一次,否则会有类重复的错误,但可以存在相同的包目录
覆盖等级为:buildTypes > productFlavors > main

简言之:[详见‘文件内容详情图示’]
main目录中资源代码内容都是公共的,java目录下的代码文件是唯一的,其他版本目录不可存在;res目录下的资源文件(如string.xml)可以在其他版本目录存在,但其中的文件内容必须是唯一的。
其他各版本目录下的可以存在名称相同的文件,文件中的资源代码内容皆为各自定制独有的

具体请看官网:https://developer.android.com/studio/build/manifest-merge.html (需要翻墙)

2、更换各自版本资源代码内容:
基础版本或者马甲的页面风格各不相同,可能还会有某处的逻辑跳转等也不相同,因此,需要在相应的文件夹中放入定制化的代码或者资源文件,如图标,启动页等【例如‘文件内容详情图示’中所示的app_name就需要更换成各自版本的名称】

3、配置产品变体版本:
在app的app/build.gradle 文件中加入

productFlavors {
        base {    //基础版本
            applicationId "com.ylzt.apcapp"
            versionCode 20
            versionName "2.0"
        }

        mP1 {    //马甲1
            applicationId "com.ylzt.apcapp.mp1"
            versionCode 10
            versionName "1.0"
        }
    }

其中可构建的内容还有很多,具体可以参考:https://developer.android.com/studio/build/build-variants.html

4、配置友盟渠道:
一个产品版本的时候,是要把友盟各个渠道都配置到productFlavors 中的,如下:

productFlavors {
        playStore {}
        miui {}
        wandoujia {}
        //......
}

还有在AndroidManifest.xml中配置标签:

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

但是现在将产品作为构建变体,所以就需要使用组合了,即flavorDimensions 属性,来创建两种模式,一种作为产品变体(“verf”),一种作为渠道变体(“channel”),将各个变体进行分组,并通过dimension 来区分所要构建的变体类型,即可完成相应的组合。
注:其中的vcase变量是用来区分产品的,在上面版本一中提到了。fname是用来过滤版本的

配置如下:

    def vcase     //版本马甲编号,第二版打包方式定义变量
    flavorDimensions "verf", "channel"    //第二版打包方式
    productFlavors {
        base {    //基础版本
            dimension "verf"
            applicationId "com.ylzt.apcapp.debug"
//            applicationId "com.ylzt.apcapp"
            vcase = "0"
            fname = "base"
            versionCode 20
            versionName "2.0"
            if (applicationId.endsWith('.debug')) { //debug
                manifestPlaceholders = [
                        JPUSH_APPKEY: "e3b5c9a23c91fe0154ce1e97",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.debug"
                ]
            } else {    //release
                manifestPlaceholders = [
                        JPUSH_APPKEY: "0cf7784913f4ec3ce1cf0eb8",
                        JPUSH_PKGNAME: "com.ylzt.apcapp"
                ]
            }
        }

        mP1 {    //马甲1
            dimension "verf"
            applicationId "com.ylzt.apcapp.mp1.debug"
//            applicationId "com.ylzt.apcapp.mp1"
//            vcase = "1"
//            fname = "mP1"
            versionCode 10
            versionName "1.0"
            if (applicationId.endsWith('.debug')) { //debug
                manifestPlaceholders = [
                        JPUSH_APPKEY: "e1313b5e0150cf749cee9784",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1.debug"
                ]
            } else {    //release
                manifestPlaceholders = [
                        JPUSH_APPKEY: "c9a21c73c91f3cef0f4eceb8",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1"
                ]
            }
        }

        //第二版打包方式
        //友盟各个渠道编号
        "00${vcase}0001" { dimension "channel" }
        "00${vcase}0002" { dimension "channel" }
        "00${vcase}0003" { dimension "channel" }
        "00${vcase}0004" { dimension "channel" }
        "00${vcase}0005" { dimension "channel" }
        "00${vcase}0006" { dimension "channel" }
        "00${vcase}0007" { dimension "channel" }
        "00${vcase}0008" { dimension "channel" }
        "00${vcase}0009" { dimension "channel" }
        "00${vcase}0010" { dimension "channel" }
        "00${vcase}0011" { dimension "channel" }
        "00${vcase}0012" { dimension "channel" }
        "00${vcase}0013" { dimension "channel" }
        "00${vcase}0014" { dimension "channel" }
        "00${vcase}0015" { dimension "channel" }
        "00${vcase}0016" { dimension "channel" }
        "00${vcase}0017" { dimension "channel" }
        "00${vcase}0018" { dimension "channel" }
        "00${vcase}0019" { dimension "channel" }
        "00${vcase}0020" { dimension "channel" }
        "00${vcase}0021" { dimension "channel" }
        "00${vcase}0022" { dimension "channel" }
        "00${vcase}0023" { dimension "channel" }
        "00${vcase}0024" { dimension "channel" }
        "00${vcase}0025" { dimension "channel" }
        "00${vcase}0026" { dimension "channel" }
        "00${vcase}0027" { dimension "channel" }
        "00${vcase}0028" { dimension "channel" }
        "00${vcase}0029" { dimension "channel" }
        "00${vcase}0030" { dimension "channel" }
        "00${vcase}0031" { dimension "channel" }
        "00${vcase}0032" { dimension "channel" }
        "00${vcase}0033" { dimension "channel" }
        "00${vcase}0034" { dimension "channel" }
        "00${vcase}0035" { dimension "channel" }
        "00${vcase}0036" { dimension "channel" }
        "00${vcase}0037" { dimension "channel" }
        "00${vcase}0038" { dimension "channel" }
        "00${vcase}0039" { dimension "channel" }
        "00${vcase}0040" { dimension "channel" }
        "00${vcase}0041" { dimension "channel" }
        "00${vcase}0042" { dimension "channel" }
        "00${vcase}0043" { dimension "channel" }
        "00${vcase}0044" { dimension "channel" }
        "00${vcase}0045" { dimension "channel" }
        "00${vcase}0046" { dimension "channel" }
        "00${vcase}0047" { dimension "channel" }
        "00${vcase}0048" { dimension "channel" }
        "00${vcase}0049" { dimension "channel" }
        "00${vcase}0050" { dimension "channel" }
        "00${vcase}0051" { dimension "channel" }
        "00${vcase}0052" { dimension "channel" }
        "00${vcase}0053" { dimension "channel" }
        "00${vcase}0054" { dimension "channel" }
        "00${vcase}0055" { dimension "channel" }
        "00${vcase}0056" { dimension "channel" }
        "00${vcase}0057" { dimension "channel" }
        "00${vcase}0058" { dimension "channel" }
        "00${vcase}0059" { dimension "channel" }
        "00${vcase}0060" { dimension "channel" }
        /**/
    }

    productFlavors.all { flavor ->
        if (dimension=="channel") {
            flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
        }
    }

    //变种过滤
    variantFilter { variant ->
        def names = variant.flavors*.name
        // To check for a certain build type, use variant.buildType.name == "<buildType>"
        if (!names.contains(fname)) {
            // Gradle ignores any variants that satisfy the conditions above.
            setIgnore(true)
        }
    }

在测试运行的时候,可以选择相应的版本进行运行,如图:

构建变体选择图示

5、相关问题总结:

  • 问题一:微信分享支付的回调
    根据文档微信分享必须在对应的包名下创建wxapi/WXEntryActivity 才能回调,所以需要在各自的目录下新建各自版本的wxapi/WXEntryActivity 来保证唯一(注意包名),并在各版本下的AndroidManifest.xml中注册,如图:
微信回调Java文件base目录图示
微信回调Java文件mp1目录图示
AndroidManifest.xml图示
  • 问题二:极光推送相关问题:
    极光推送比较坑爹的是,开发环境和生产环境不能使用同一个appkey,所以面临的问题就是:有多少个产品应用,就需要申请双倍的appkey,比如上面我们有base和mP1两个产品,就需要申请四个appkey,分别为:
    base的appkey:xxxxxxbase
    base的debug_appkey:xxxxxxbaseDebug
    mP1的appkey:xxxxxxmP1
    mP1的debug_appkey:xxxxxxmP1Debug

注:这里的后缀用于区分而已,实际appkey形如:cce012wwf66bd554999a56w6

当然了,每一个的包名都不能相同,对于这种情况,就需要在构建变体的时候,通过后缀来判断使用哪一个appkey了:

productFlavors {
        base {    //基础版本
            applicationId "com.ylzt.apcapp.debug"  //打包的时候关闭注释
//            applicationId "com.ylzt.apcapp"  //打包的时候打开注释
            versionCode 20
            versionName "2.0"
            if (applicationId.endsWith('.debug')) { //debug
                manifestPlaceholders = [
                        JPUSH_APPKEY: "e3b5c9a23c91fe0154ce1e97",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.debug"
                ]
            } else {    //release
                manifestPlaceholders = [
                        JPUSH_APPKEY: "0cf7784913f4ec3ce1cf0eb8",
                        JPUSH_PKGNAME: "com.ylzt.apcapp"
                ]
            }
        }

        mP1 {    //马甲1
            applicationId "com.ylzt.apcapp.mp1.debug"  //打包的时候关闭注释
//            applicationId "com.ylzt.apcapp.mp1"  //打包的时候打开注释
            versionCode 10
            versionName "1.0"
            if (applicationId.endsWith('.debug')) { //debug
                manifestPlaceholders = [
                        JPUSH_APPKEY: "e1313b5e0150cf749cee9784",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1.debug"
                ]
            } else {    //release
                manifestPlaceholders = [
                        JPUSH_APPKEY: "c9a21c73c91f3cef0f4eceb8",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1"
                ]
            }
        }
    }

在AndroidManifest.xml中配置极光相关代码:

        <meta-data
            android:name="JPUSH_CHANNEL"
            android:value="developer-default" />
        <meta-data
            android:name="JPUSH_APPKEY"
            android:value="${JPUSH_APPKEY}" />

        <!-- 包名引用举例-->
        <activity
            android:name="cn.jpush.android.ui.PushActivity"
            android:configChanges="orientation|keyboardHidden"
            android:exported="false"
            android:theme="@android:style/Theme.NoTitleBar">
            <intent-filter>
                <action android:name="cn.jpush.android.ui.PushActivity" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="${JPUSH_PKGNAME}" />
            </intent-filter>
        </activity>

通过这种方式就可以分别使用各自环境下的appkey了,不过有一点麻烦的是发版打包的时候,需要手动打开和关闭相应的注释。

我也考虑过使用applicationIdSuffix ".debug" ,即在配置debug 的时候使用applicationIdSuffix 属性,这样就可以不用去手动打开和关闭注释,因为这两个环境下的applicationId只是差一个“.debug”的后缀而已。但是这样会出现一个问题,就是在开发环境下,会收不到极光推送的消息,原因何在呢?

可以肯定的是,debug版本的应用并未注册到极光推送的服务器上,即并未获得RegistrationId。我个人觉得,实际已经注册了,但是注册的是release版本,即生产环境的版本。这其中似乎是引用的问题,下面我用伪代码以base版本为例来说一说我的猜测:

applicationIdSuffix = ".debug"
applicationId = "com.ylzt.apcapp"
applicationId_result = applicationId + applicationIdSuffix  //com.ylzt.apcapp.debug
if (applicationId.endsWith('.debug')) { //debug
    manifestPlaceholders = [
        JPUSH_APPKEY: "e1313b5e0150cf749cee9784",
        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1.debug"
     ]
 } else {    //release
    manifestPlaceholders = [
        JPUSH_APPKEY: "c9a21c73c91f3cef0f4eceb8",
        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1"
    ]
}

个人觉得两种可能,一种就是伪代码中所示的,是用applicationId_result 作为最终的applicationId的引用;另一种就是applicationId没有及时赋值,虽然是用applicationId作为应用,但是当完成赋值的时候,已经执行了if语句了。

所以才会出现上面我说到的情况,当然,这是本人愚见,若有哪位大神知道原因和解决方案,欢迎留言指正,谢谢。

6、修改完app/build.gradle 之后,直接在window命令行中输入命令即可:

Y:\>cd Y:\WorkPlaces\AndroidStudioWP\ApplicationDemos\app
Y:\WorkPlaces\AndroidStudioWP\ApplicationDemos\app>gradlew assembleRelease
命令行截图

缺点:
耗时——渠道越多,所用时间越长
......


版本三:整合项目,神器打包

到此,放大招了,成为终极打包版本吧,用到的神器就是Walle,参考:https://github.com/Meituan-Dianping/walle

注意:
参考中给定的渠道channel文件是放在app文件夹同级目录中的,而由于本人的项目中的渠道是自定义的编码,每个产品的第二位都不一样,所以就需要放到各自版本的目录之中,位置如图:

渠道目录图示

闲话少说,直接上代码:

build.gradle:

apply plugin: 'com.android.application'
apply plugin: 'walle'

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

def fname     //版本马甲名称
android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.ylzt.apcapp"
        minSdkVersion 15
        targetSdkVersion 25

        ndk {
            //选择要添加的对应cpu类型的.so库。
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
            // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
        }

        multiDexEnabled true

        versionCode 1
        versionName "1.0"
    }

    signingConfigs {
        debug {
            storeFile file("debug.keystore")

            storePassword "123456"
            keyAlias "ylzt"
            keyPassword "123456"

        }

        release {
            storeFile file("Y:/ylzt.keystore")

            storePassword "123456"
            keyAlias "ylzt"
            keyPassword "123456"
        }
    }

    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles 'proguard-rules.pro'
        }

        release {
            // 不显示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"

            // 是否进行混淆
            minifyEnabled true
            // 混淆文件的位置
            proguardFiles 'proguard-rules.pro'
            signingConfig signingConfigs.release

            shrinkResources true  // 移除无用的resource文件
        }
    }

    productFlavors {
        base {    //基础版本
            applicationId "com.ylzt.apcapp.debug"
//            applicationId "com.ylzt.apcapp"
            fname = "base"  //需要手动修改,以改变要打包的项目
            versionCode 20
            versionName "2.0"
            if (applicationId.endsWith('.debug')) { //debug
                manifestPlaceholders = [
                        JPUSH_APPKEY: "e3b5c9a23c91fe0154ce1e97",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.debug"
                ]
            } else {    //release
                manifestPlaceholders = [
                        JPUSH_APPKEY: "0cf7784913f4ec3ce1cf0eb8",
                        JPUSH_PKGNAME: "com.ylzt.apcapp"
                ]
            }
        }

        mP1 {    //马甲1
            applicationId "com.ylzt.apcapp.mp1.debug"
//            applicationId "com.ylzt.apcapp.mp1"
//            fname = "mP1"  //需要手动修改,以改变要打包的项目
            versionCode 10
            versionName "1.0"
            if (applicationId.endsWith('.debug')) { //debug
                manifestPlaceholders = [
                        JPUSH_APPKEY: "e1313b5e0150cf749cee9784",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1.debug"
                ]
            } else {    //release
                manifestPlaceholders = [
                        JPUSH_APPKEY: "c9a21c73c91f3cef0f4eceb8",
                        JPUSH_PKGNAME: "com.ylzt.apcapp.mp1"
                ]
            }
        }

    }

    //变种过滤
    variantFilter { variant ->
        def names = variant.flavors*.name
        // To check for a certain build type, use variant.buildType.name == "<buildType>"
        if (!names.contains(fname)) {
            // Gradle ignores any variants that satisfy the conditions above.
            setIgnore(true)
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:design:25.3.1'
    testCompile 'junit:junit:4.12'

    /*依赖module*/
    compile project(':..:LibsModules/:core')

    compile 'com.umeng.analytics:analytics:latest.integration'
    //wx
    compile files('libs/libammsdk.jar')
    compile files('libs/SocialSDK_WeChat_Simplify.jar')

    compile 'com.meituan.android.walle:library:1.1.4'
}



//第三版打包方式:walle打包方式,详见https://github.com/Meituan-Dianping/walle
walle {
    // 指定渠道包的输出路径
    apkOutputFolder = new File("${project.buildDir}/outputs/channels");
    // 定制渠道包的APK的文件名称
    apkFileNameFormat = '${flavorName}_0${versionCode}_${channel}.apk';
    // 渠道配置文件
//    channelFile = new File("${project.getProjectDir()}/channel")
    channelFile = new File("${project.getProjectDir()}/src/${fname}/channel")
}

注:其中需要手动修改的地方已经写了注释,即以fname变量来控制打包工程,在变种过滤(variantFilter)中已经做了相应的判断

用这种方式打包确实很爽,不到一分钟,60个包全部完成,妈妈再也不用担心我打包耗时了~~~

结语

经过三个版本的演化,从中学到了不少,gradle的知识还有很多需要学习,希望大家能共同进步。
当然了,其中还有许多要改进的地方,还没有完全做到全自动化,有兴趣的同学可以看一看云打包的相关资料,有好的学习资料也希望大家多多分享。

相关资料:

https://developer.android.com/studio/build/build-variants.html(需要翻墙)
https://developer.android.com/studio/build/manifest-merge.html(需要翻墙)

https://segmentfault.com/a/1190000002910311
https://github.com/Meituan-Dianping/walle
http://tech.meituan.com/android-apk-v2-signature-scheme.html

http://www.jianshu.com/p/c90e21bf4e23?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容