Android多渠道打包这样做才酸爽!?

前言

Contents

多渠道主要目的是为了统计各个应用市场用户数据分析(比如活跃数,崩溃率等),收集用户信息,这时需要唯一标识来区分这些渠道,本文主要针对多渠道(几百个渠道甚至更多的情况)如何快速打包?

目的

  • Jenkins集成Gradle实现打包自动化
  • 通过Jenkins参数化构建实现自定义环境和渠道打包,签名
  • 测试包自动上传fir并通过钉钉发送通知
  • 正式包按版本归档到OSS,发布时拷贝包到发布目录
  • 自动刷新CDN

环境说明

  • 系统 : Centos6.5 x64
  • jdk-7u79-linux-x64
  • android-sdk_r24.4.1-linux
  • gradle-2.2.1
  • Python-2.7.10(操作DingTalk和OSS API)
  • Jenkins2.0/Tomcat-7.0.65

配置环境

1.安装JDK

wget 'http://download.oracle.com/otn-pub/java/jdk/7u79-b15/jdk-7u79-linux-x64.tar.gz?AuthParam=1460974294_526e0f8471004294cb163c9c730ba4f9' -O jdk1.7.0_79.tar.gz
tarxfjdk-7u79-linux-x64.tar.gz -C /usr/local/jdk1.7.0_79
 

2.安装Python2.7.10

wgethttps://www.python.org/ftp/python/2.7.10/Python-2.7.10.tgz
tarxfPython-2.7.10.tgz
cdPython-2.7.10
./configure
make -j4
makeinstall
sed -i 's#python/python2.6#g' /usr/bin/yum
 

3.安装Android的SDK

wgethttp://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
tarxfandroid-sdk_r24.4.1-linux.tgz -C /usr/local/android-sdk-linux
 

4.安装tomcat和jenkins

yum -y installtomcat
wgethttp://mirrors.jenkins-ci.org/war-rc/2.0/jenkins.war -O /usr/share/tomcat/webapps/jenkins.war
 

5.配置环境变量,启动服务

vim /etc/profile
exportANDROID_HOME=/usr/local/android-sdk-linux
exportJAVA_HOME=/usr/local/jdk1.7.0_80
exportPATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
source /etc/profile
servicetomcatstart
Jenkins访问地址:http://192.168.2.2:8080/jenkins/
 

6.安装Android SDK依赖包

由于AndroidSDK工具基于32位在64位系统上需要安装32位必须安装的i386依赖库
yuminstall -y glibc.i686glibc-devel.i686libstdc++.i686zlib-devel.i686ncurses-devel.i686libX11-devel.i686libXrender.i686libXrandr.i686
 

####安装更新对应版本的SDK

由于国内直接解析访问dl.google.com,dl-ssl.google.com域名较慢,可以通过更改hosts方式解决:
digdl.google.comdl-ssl.google.com
将获得IP写入/etc/hosts,例如:
203.208.43.110 dl.google.com
74.125.23.91 dl-ssl.google.com
查看SDK相关列表
android  list sdk --all
Packagesavailablefor installationor update: 150
  1- AndroidSDKTools, revision 25.1.1
  2- AndroidSDKTools, revision 25.1.3 rc1
  3- AndroidSDKPlatform-tools, revision 23.1
  4- AndroidSDKPlatform-tools, revision 24 rc2
  5- AndroidSDKBuild-tools, revision 24 rc3
  6- AndroidSDKBuild-tools, revision 23.0.3
  7- AndroidSDKBuild-tools, revision 23.0.2
  8- AndroidSDKBuild-tools, revision 23.0.1
  9- AndroidSDKBuild-tools, revision 23 (Obsolete)
  10- AndroidSDKBuild-tools, revision 22.0.1
  11- AndroidSDKBuild-tools, revision 22 (Obsolete)
  12- AndroidSDKBuild-tools, revision 21.1.2
  13- AndroidSDKBuild-tools, revision 21.1.1 (Obsolete)
  14- AndroidSDKBuild-tools, revision 21.1 (Obsolete)
  15- AndroidSDKBuild-tools, revision 21.0.2 (Obsolete)
  16- AndroidSDKBuild-tools, revision 21.0.1 (Obsolete)
  17- AndroidSDKBuild-tools, revision 21 (Obsolete)
  18- AndroidSDKBuild-tools, revision 20
  19- AndroidSDKBuild-tools, revision 19.1
  20- AndroidSDKBuild-tools, revision 19.0.3 (Obsolete)
  21- AndroidSDKBuild-tools, revision 19.0.2 (Obsolete)
  22- AndroidSDKBuild-tools, revision 19.0.1 (Obsolete)
  23- AndroidSDKBuild-tools, revision 19 (Obsolete)
  24- AndroidSDKBuild-tools, revision 18.1.1 (Obsolete)
  ...
选择要安装项目的序号
android  updatesdk -u -a -t 5,6,7,31,34,136,137 
 

手动编译测试Android项目

gitclone [email protected]:android/Android_demo.git
cdAndroid_demo
查看当前项目包含的tasks(此时若无gradle会自动下载安装)
./gradlew  tasks
清空build目录
./gradlewclean
编译打包所有环境包
./gradlewassemble
编译打包Debug包
./gradlewassembleDebug
编译打包Release包
./gradlewassembleRelease
 

多渠道打包项目改造

  1. 包的签名在build.gradle中配置,打包后自动签名
  2. 由于META-INF目录下是存放签名信息的,用来保证apk包的完整性和安全,在生成apk时对文件做校验计算并把结果存放在META-INF目录中,安装apk包时应用管理器会按照同样的算法对包里的文件做校验,如果和META-INF中的内容不一致,则无法安装,通过修改apk包在重新打包基本不可能,以此来保证apk包的安全,因此在打完第一个包时,可以在META-INF目录中添加一个channel_wandoujia空文件,代码匹配这个文件获取渠道名wandoujia,来快速实现多渠道打包的目的
  3. 代码库根目录channel文件存放渠道名
apk包解压后目录结构:
├── AndroidManifest.xml
├── assets
├── classes.dex
├── lib
├── META-INF
│   ├── CERT.RSA
│   ├── CERT.SF
│   ├── channel_huawei
│   └── MANIFEST.MF
├── org
├── res
└── resources.arsc
 
Gradle(apk通过gradle签名)例子app/build.gradle:
applyplugin: 'com.android.application'
android {
    compileSdkVersion 22
    buildToolsVersion '23.0.2'
    defaultConfig {
        applicationId "com.maka.app"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 16
        versionName '2.0.0'
 
        //dex突破65535的限制
        multiDexEnabledtrue
    }
    dexOptions {
        jumboMode = true
        incrementaltrue
        javaMaxHeapSize "4g"
        preDexLibraries = false
        incrementaltrue
    }
    signingConfigs {
        debug {
            storeFilefile("../key.jks")
            storePassword "helloworld"
            keyAlias "helloworld"
            keyPassword "helloworld"
        }
 
 
    }
    packagingOptions {
        exclude 'META-INF/LICENCE.txt'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/NOTICE.txt'
    }
    lintOptions {
        checkReleaseBuildsfalse
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
        abortOnErrorfalse
    }
    buildTypes {
        debug {
            // 显示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"
            versionNameSuffix "-debug"
            minifyEnabledfalse
            zipAlignEnabledtrue
            shrinkResourcesfalse
            signingConfigsigningConfigs.debug
            manifestPlaceholders = [
                    UMENG_APP_KEY  : "556ac653162s58e06c0000218",
                    UMENG_APP_SECRET: "2a231041d6aa10ec2b2s933003135a7"
            ]
            //Server config
            buildConfigField "boolean", "SELECT_SERVER", "true"
            buildConfigField "String", "TEST_IP", ""http://test.api.simlinux.com/""
            buildConfigField "String", "TEST_PROJECT_URL", ""http://test.viewer.simlinux.com/k/""
            buildConfigField "String", "TEST_PICTURE_URL", ""http://test.img1.simlinux.com/""
            buildConfigField "String", "TEST_RES_URL", ""http://test.res.simlinux.com/""
            buildConfigField "String", "FORMAL_IP", ""http://api.simlinux.com/""
            buildConfigField "String", "FORMAL_PROJECT_URL", ""http://viewer.simlinux.com/k/""
            buildConfigField "String", "FORMAL_PICTURE_URL", ""http://img1.simlinux.com/""
            buildConfigField "String", "FORMAL_RES_URL", ""http://res.simlinux.com/""
 
        }
        release {
            // 不显示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"
            minifyEnabledtrue
            zipAlignEnabledtrue
            // 移除无用的resource文件
            shrinkResourcestrue
            proguardFile 'proguard-project.txt'
            debuggablefalse
            shrinkResourcesfalse
            signingConfigsigningConfigs.debug
            manifestPlaceholders = [
                    UMENG_APP_KEY  : "556ac6s3162358e06c0000218",
                    UMENG_APP_SECRET: "2a231041d6aa10ec2b2s933003135a7"
            ]
            //Server config
            buildConfigField "boolean", "SELECT_SERVER", "false"
            buildConfigField "String", "TEST_IP", """"
            buildConfigField "String", "TEST_PROJECT_URL", """"
            buildConfigField "String", "TEST_PICTURE_URL", """"
            buildConfigField "String", "TEST_RES_URL", """"
            buildConfigField "String", "FORMAL_IP", ""http://api.simlinux.com/""
            buildConfigField "String", "FORMAL_PROJECT_URL", ""http://viewer.simlinux.com/k/""
            buildConfigField "String", "FORMAL_PICTURE_URL", ""http://img1.simlinux.com/""
            buildConfigField "String", "FORMAL_RES_URL", ""http://res.simlinux.com/""
 
        }
 
    }
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            defoutputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                deffileName = outputFile.name.replace(".apk", "-${defaultConfig.versionName}.apk")
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }
    productFlavors {
    }
 
}
 
repositories {
    flatDir {
        dirs 'libs' //this way we can find the .aar file in libs
    }
}
dependencies {
    compile 'com.google.code.gson:gson:2.3.1'
    compile 'com.github.japgolly.android:svg-android:2.0.6'
    compilefileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.github.rey5137:material:1.2.1'
    compile 'com.squareup.okhttp:okhttp-apache:2.4.0'
    compile(name: 'vds-sdk-release', ext: 'aar')
    compile 'com.android.support:multidex:1.0.0'
    compileproject(':PushSDK')
    compile 'com.google.zxing:core:3.2.1'
    compile 'com.android.support:recyclerview-v7:24.0.0-alpha1'
    compile 'com.rengwuxian.materialedittext:library:2.1.4'
}
 
匹配META-INF/channel_wandoujia文件名读取wandoujia渠道
 
    public static String readChanel() {
        ApplicationInfoappInfo = ContextManager.getContext().getApplicationInfo();
        String sourceDir = appInfo.sourceDir;
        String ret = "";
        ZipFilezipfile = null;
        Log.i(TAG, "---begin-ret=" + ret);
        try {
            zipfile = new ZipFile(sourceDir);
            Enumeration entries = zipfile.entries();
            while (entries.hasMoreElements()) {
                ZipEntryentry = ((ZipEntry) entries.nextElement());
                String entryName = entry.getName();
                if (entryName.startsWith("META-INF/channel")) {
                    ret = entryName;
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
 

Android多渠道打包流程

基于上述方式实现多渠道打包流程如下:

  • 执行gradlew clean清除build目录
  • 执行gradlew assemble编译打包Debug/Release(已自动签名)
  • 上传Debug包到Fir
  • 通过DingTalk发送通知信息到QA讨论组(发送提测apk包版本,下载地址及扫描下载二维码)
  • 提测不通过,修复bug后再次执行前四步
  • 提测通过后,点击Jenkins打包归档多渠道按钮,将执行生成多渠道包并归档包到本地目录/data/2.0.1/xxx.apk
  • 可选择此步上传归档文件到OSS
  • 点击Jenkins发布按钮将最新版本相关渠道归档拷贝至OSS发布目录
  • 刷新CDN生效
  • 通过DingTalk发送通知信息到QA讨论组哪些渠道已经发布

配置步骤

配置Jenkins

插件: DynamicChoiceParameter
**创建打包测试项目:Android-Test**
 

通过Groovy脚本获取分支
defver_keys = [ 'bash', '-c', 'cd /usr/share/tomcat/.jenkins/workspace/Android-Test;git branch -a|grep remotes|cut -d "/" -f3|grep -v HEAD|sort' ]
    ver_keys.execute().text.tokenize('n')
 
构建脚本
#!/bin/bash
 
PATH=/usr/lib64/qt-3.3/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/local/jdk1.7.0_80/bin:/usr/local/android-sdk-linux/tools:/usr/local/android-sdk-linux/platform-tools:/usr/local/gradle-2.2.1/bin:/usr/share/tomcat/bin
 
cd ${WORKSPACE}
git checkout ${BranchToDeploy}
gitpull -f
 
if [ "${EnvToDeploy}"  = "All Env" ];then
 
        ${WORKSPACE}/gradlew clean
        ${WORKSPACE}/gradlew assemble
 
else
        ${WORKSPACE}/gradlew clean
        ${WORKSPACE}/gradlew assemble${EnvToDeploy}
 
fi
#测试包上传fir,发钉钉通知
/usr/local/bin/python /usr/share/tomcat/AndroidDeploy/androidtest.py
 

创建多渠道包归档项目:Android-Archive

获取channel列表
defver_keys = [ 'bash', '-c', 'echo "All Channels"; cat /usr/share/tomcat/.jenkins/workspace/MAKA-Android-Testing/channels' ]
ver_keys.execute().text.tokenize('n')
构建脚本,更改渠道文件,上传OSS,发送钉钉通知
python /usr/share/tomcat/AndroidDeploy/androidarchive.py
 

创建发布多渠道包项目:Android-Deploy

构建脚本,通过OSSAPI拷贝要发布的归档渠道包到发布目录,发送钉钉通知
python /usr/share/tomcat/AndroidDeploy/androiddeploy.py
 

相关脚本

├── androidarchive.py      多渠道打包归档脚本
├── androiddeploy.py        渠道包发布脚本
├── androidtest.py          测试打包脚本
└── libs
    ├── chinanetcenter.py  刷新CDN脚本
    ├── dingtalk.py        钉钉发送消息,图片,分享脚本
    ├── fir.py              测试包上传fir脚本
    ├── __init__.py
    └── libsoss.py          oss相关操作脚本(上传,拷贝等)
 
具体代码可根据https://github.com/geekwolf/AppDeployment按照实际业务进行修改
 

IOS打包流程

  • xcodebuild clean 清理build目录
  • xcodebuild archive 选择不同的环境/BundleID/ProvisionProfile/CodeSigningIdentify 编译,签名生成xcarchive文件放到工程根路径下的 build 文件夹里
  • xcodebuild -exportArchive 打包生成ipa
  • 测试包自动上传Fir,生产包手动更新AppStore
  • 具体可参考脚本 https://github.com/geekwolf/AppDeployment/blob/master/IOSDeploy.sh

总结

任何自动化的前提必须先规范化,针对Android多渠道打包渠道命名,apk包命名需要先统一,apk包不要多环境混用(生产环境和测试环境要分离,测试包可自定义切换);到了这里,会发现我TM乱七八糟搞了这一陀哪里酸爽了?另外一个思路是通过修改apk文件的注释,程序在启动时读取apk文件注释获取渠道名(但是Android系统直到API 19,也就是4.4以上的版本才支持data/app/

.apk)

爽在哪里?

1. 打包不再需要开发本地执行(避免中断开发,多人协作时优势更为明显)

2. 多渠道打包时间在于第一个包编译生成和签名的时间,之后的无论多少渠道都只是修改包的META-INF/channel_wandoujia空文件名实现

3. 点下Jenkins按钮无需在等待打包过程,打包完成后发送消息到钉钉会话,这下爽了吗?

参考文档

Gradle入门 http://www.androidchina.net/2155.htmlAndroid签名 http://www.tuicool.com/articles/2eMZJfu

稿源:Geekwolf's Blog (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 后端存储 » Android多渠道打包这样做才酸爽!?

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录