神刀安全网

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 git@git.maka.mobi: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**   

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

通过Groovy脚本获取分支      def ver_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

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

获取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乱七八糟搞了这一陀哪里酸爽了?

爽在哪里?

  1. 打包不再需要开发本地执行(避免中断开发,多人协作时优势更为明显)
  2. 多渠道打包时间在于第一个包编译生成和签名的时间,之后的无论多少渠道都只是修改包的META-INF/channel_wandoujia空文件名实现
  3. 点下Jenkins按钮无需在等待打包过程,打包完成后发送消息到钉钉会话,这下爽了吗?

参考文档

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

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Android多渠道打包这样做才酸爽!?

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址