我的gradle多渠道打包记录

446 查看

关于android数字签名的作用,参见:http://blog.sina.com.cn/s/blog_4a4f9fb50101db1f.html

生成keystore

参见官网签名说明文档:http://developer.android.com/intl/zh-cn/tools/publishing/app-signing.html

在android studio中

build->generate signed APK->create new.按顺序填写之后生成签名所需的keystore文件。

用命令行

keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000

按上图命令分别输入的是签名文件名,别名,指明生成的是2048位RSA秘钥,签名有效期。

我的打包记录

这部分内容参考自:http://www.stormzhang.com/devtools/2015/01/15/android-studio-tutorial6/

android studio 多渠道打包CMD命令:gradle assembleRelease
需要在命令提示行(管理员)中定位到项目位置,然后输入gradle初始化gradle环境,然后输入如上命令。

我的gradle打包示例一

android studio中build.gradle示例配置如下,将全部渠道名写入channel.txt文件中,放到app文件夹下面,并将keystore文件放到相同位置。新添加渠道的时候不需要改动build.gradle文件,只需在channel.txt文件中添加新的渠道名就可以了。打包完成之后会生成未签名与签名的两种apk包:

apply plugin: 'com.android.application'

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

def keyStore = file('sign.keystore')

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.0"

    defaultConfig {
        applicationId "com.zrp.test"
        minSdkVersion 9
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }

    packagingOptions {
        exclude 'META-INF/DEPENDENCIES.txt'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/dependencies.txt'
        exclude 'META-INF/LGPL2.1'
    }

    // Remove warnings
    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
    }

    // productFlavors
    productFlavors {
        def path = "./channel.txt"
        file(path).eachLine { channel ->
            "$channel" {
                manifestPlaceholders = [UMENG_VALUE: channel]
            }
        }
    }

    signingConfigs {
        app {
            storeFile file('sign.keystore')
            storePassword project.hasProperty('STOREPASS') ? STOREPASS : '你的秘钥库口令'
            keyAlias project.hasProperty('KEYALIAS') ? KEYALIAS : '别名'
            keyPassword project.hasProperty('KEYPASS') ? KEYPASS : '秘钥口令'
        }
    }

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

            debuggable false
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            if (keyStore.exists()) {
                println "using test key"
                signingConfig signingConfigs.app
            } else {
                println "---------------using default key---------------"
            }

            android.applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        // 输出apk名称为test_v1.0_2015-01-15_wandoujia.apk
                        def fileName = "test_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
        }
    }
}
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:22.+'
}

如上gradle文件中,如果未使用签名文件打包,会出现INSTALL_PARSE_FAILED_NO_CERTIFICATES,或INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION错误,导致安装失败。所以,一定要使用签名文件进行打包签名,不然用会导致应用安装失败!

我的gradle打包示例二

apply plugin: 'com.android.application'

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

android {
    compileSdkVersion 22
    buildToolsVersion '23.0.0'

    defaultConfig {
        applicationId "com.zrp.test"
        minSdkVersion 9
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"

        // dex突破65535的限制
        multiDexEnabled true
        // 默认是umeng的渠道
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
    }

    lintOptions {
        abortOnError false
    }

    signingConfigs {
        debug {
            // No debug config
        }

        release {
            storeFile file("../sign.keystore")
            storePassword "秘钥库口令"
            keyAlias "别名"
            keyPassword "秘钥口令"
        }
    }

    buildTypes {
        debug {
            // 显示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"

            versionNameSuffix "-debug"
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.debug
        }

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

            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名称为test_v1.0_2015-01-15_wandoujia.apk
                        def fileName = "test_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
        }
    }

    // 友盟多渠道打包
    productFlavors {
        wandoujia {}
//        _360 {}
//        baidu {}
//        xiaomi {}
//        tencent {}
//        taobao {}
    }

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

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.+'
}

如果在打包的时候报Unable to compute hash of /../AndroidStudioProjects/../classes.jar错误,说明在打包混淆的时候需要keep一些文件,让他们不要被混淆。
可以在proguard-rules.pro文件中添加keep,单独添加的第三方包需要再次添加。

打包完成之后生成的签名包在.\app\build\outputs\apk文件夹下。

美团的打包方案(现阶段最快)

这个方案依赖于google的签名机制,如果google改变android的签名机制的话这个方案就无法使用了
Github上有人写了这个方法的库:https://github.com/GavinCT/AndroidMultiChannelBuildTool,其博客讲解网址为:http://www.cnblogs.com/ct2011/p/4152323.html

美团的打包原文网址:http://tech.meituan.com/mt-apk-packaging.html
如下为原文摘抄内容:

META-INF
如果能直接修改apk的渠道号,而不需要再重新签名能节省不少打包的时间。幸运的是我们找到了这种方法。直接解压apk,解压后的根目录会有一个META-INF目录,如下图所示:

如果在META-INF目录内添加空文件,可以不用重新签名应用。因此,通过为不同渠道的应用添加不同的空文件,可以唯一标识一个渠道。

下面的python代码用来给apk添加空的渠道文件,渠道名的前缀为mtchannel_:

import zipfile
zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED) 
empty_channel_file = "META-INF/mtchannel_{channel}".format(channel=your_channel)
zipped.write(your_empty_file, empty_channel_file)

添加完空渠道文件后的目录,META-INFO目录多了一个名为mtchannel_meituan的空文件:

接下来就可以在Java代码中读取空渠道文件名了:

public static String getChannel(Context context) {
        ApplicationInfo appinfo = context.getApplicationInfo();
        String sourceDir = appinfo.sourceDir;
        String ret = "";
        ZipFile zipfile = null;
        try {
            zipfile = new ZipFile(sourceDir);
            Enumeration<?> entries = zipfile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = ((ZipEntry) entries.nextElement());
                String entryName = entry.getName();
                if (entryName.startsWith("mtchannel")) {
                    ret = entryName;
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        String[] split = ret.split("_");
        if (split != null && split.length >= 2) {
            return ret.substring(split[0].length() + 1);

        } else {
            return "";
        }
    }

这样,每打一个渠道包只需复制一个apk,在META-INF中添加一个使用渠道号命名的空文件即可。这种打包方式速度非常快,900多个渠道不到一分钟就能打完。