Ant、Gradle、Python三种打包方式的介绍

来源:互联网 发布:如何查看知乎提问者 编辑:程序博客网 时间:2024/04/30 11:11

博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !

创建签名
keytool -genkey -v -keystore stone.keystore -alias stone -keyalg RSA -keysize 2048-validity 10000 生成签名文件
为apk签名
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore stone.keystore unsigned.apk stone 不生成新文件
检测apk是否签名
jarsigner -verbose -certs -verify signed.apk
优化apk
zipalign -f -v 4 signed_unaligned.apk signed_aligned.apk



以上是Apk的编译过程,可以根据这个过程来编译脚本,在服务器端自动编译apk


支持Android7.0的打包方式,请直接跳到文章最后。


今天谈一下Androdi三种打包方式,Ant、Gradle、Python。

当然最开始打包用Ant 很方便,后来转Studio开发,自带很多Gradle插件就用了它,然后随着打包数量越多,打包时间成了需要考虑的事,前两者平均打一个包要花费2-3分钟,打30个就要差不多2个小时;而前两者打包的思路主要是,替换AndroidManifest.xml的meta-data下的value值,然后重新编译 注:不管Ant还是Gradle,下面这句都要加入AndroidManifest.xml

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

而Python打包非常快,几百个包5分钟以内搞定,而它的思路仅是打完一个可发布包后,往apk的META-INF下写入一个含渠道名的文件,由应用去解析这个渠道名即可,不再使用传统的meta-data去标识value值。

编译一般有以下几个步骤:

1.用aapt命令生成R.java文件

2.用aidl命令生成相应java文件

3.用javac命令编译java源文件生成class文件

4.用dx.bat将class文件转换成classes.dex文件

5.用aapt命令生成资源包文件resources.ap_

6.用apkbuilder.bat打包资源和classes.dex文件,生成unsigned.apk

7.用jarsinger命令对apk认证,生成signed.apk

-------------------------------------------------------------我是分割线----------------------------------------------------------------------------

一、简单讲一下Ant打包的流程

1、安装ant,并配置好环境变量,在ant->lib目录下放入一个ant-contrib-1.0b3.jar

2、在主项目和依赖项目目录下放置build.xml和local.properties(依赖文件只用放sdk_dir就行)

3、在主项目目录下放置custom_rules.xml即可

4、在命令行下,进入要打包的主项目目录下,输入ant deploy即可(如果二次打包要先输入ant clean)

build.xml文件如下

<?xml version="1.0" encoding="UTF-8"?><project    name="Project"    default="help" >    <property        name="project_name"        value="Project" />    <property        name="base_jar_name"        value="Common" />    <property        name="aapt.ignore.assets"        value="!.svn:!.git:\x3Cdir\x3E_*:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~:crunch" />    <!--         The local.properties file is created and updated by the 'android' tool.         It contains the path to the SDK. It should *NOT* be checked into         Version Control Systems.    -->    <property file="local.properties" />    <!--         The ant.properties file can be created by you. It is only edited by the         'android' tool to add properties to it.         This is the place to change some Ant specific build properties.         Here are some properties you may want to change/update:         source.dir             The name of the source directory. Default is 'src'.         out.dir             The name of the output directory. Default is 'bin'.         For other overridable properties, look at the beginning of the rules         files in the SDK, at tools/ant/build.xml         Properties related to the SDK location or the project target should         be updated using the 'android' tool with the 'update' action.         This file is an integral part of the build system for your         application and should be checked into Version Control Systems.    -->    <property file="ant.properties" />    <!--         if sdk.dir was not set from one of the property file, then         get it from the ANDROID_HOME env var.         This must be done before we load project.properties since         the proguard config can use sdk.dir -->    <property environment="env" />    <condition        property="sdk.dir"        value="${env.ANDROID_HOME}" >        <isset property="env.ANDROID_HOME" />    </condition>    <!--         The project.properties file is created and updated by the 'android'         tool, as well as ADT.         This contains project specific properties such as project target, and library         dependencies. Lower level build properties are stored in ant.properties         (or in .classpath for Eclipse projects).         This file is an integral part of the build system for your         application and should be checked into Version Control Systems.-->    <loadproperties srcFile="project.properties" />    <!-- quick check on sdk.dir -->    <fail        message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."        unless="sdk.dir" />    <!--        Import per project custom build rules if present at the root of the project.        This is the place to put custom intermediary targets such as:            -pre-build            -pre-compile            -post-compile (This is typically used for code obfuscation.                           Compiled code location: ${out.classes.absolute.dir}                           If this is not done in place, override ${out.dex.input.absolute.dir})            -post-package            -post-build            -pre-clean    -->    <import        file="custom_rules.xml"        optional="true" />    <!--         Import the actual build file.         To customize existing targets, there are two options:         - Customize only one target:             - copy/paste the target into this file, *before* the               <import> task.             - customize it to your needs.         - Customize the whole content of build.xml             - copy/paste the content of the rules files (minus the top node)               into this file, replacing the <import> task.             - customize to your needs.         ***********************         ****** IMPORTANT ******         ***********************         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,         in order to avoid having your file be overridden by tools such as "android update project"    -->    <!-- version-tag: 1 -->    <import file="${sdk.dir}/tools/ant/build.xml" /></project>
主要作用:声明主项目和依赖项目,sdk的位置、用到的文件如local.properties等

local.properties

## This file is automatically generated by Android Studio.# Do not modify this file -- YOUR CHANGES WILL BE ERASED!## This file must *NOT* be checked into Version Control Systems,# as it contains information specific to your local configuration.## Location of the SDK. This is only used by Gradle.# For customization when using a Version Control System, please read the# header note.#Tue Feb 16 16:07:45 CST 2016sdk.dir=AndroidSdk的位置,例如:D:\\Android_Software\\adt-bundle-windows-x86_64-20140702\\sdksdk.platformtools=YourSdkPmsdk.tools=YourSdkToolsapk.dir=打出包放的位置-打包前要确定此路径存在,且无中文app_version=版本号app_name=版本名称market_channels=渠道号-以逗号隔开key.store=密钥存储路径-注意双斜杠\\key.store.password=密码key.alias=别名key.alias.password=别名密码
最重要的custom_rules.xml来了

<?xml version="1.0" encoding="UTF-8"?><project name="custom_rules"><taskdef resource="net/sf/antcontrib/antcontrib.properties"><classpath><pathelement location="ant-libs/ant-contrib-1.0b3.jar" /></classpath></taskdef><target name="deploy"><foreach delimiter="," list="${market_channels}" param="channel"target="modify_manifest"></foreach></target><target name="modify_manifest"><replaceregexp byline="false" encoding="UTF-8" flags="g"><regexppattern="android:name="UMENG_CHANNEL"([\w\W]*)android:value="(.*)"" /><substitutionexpression="android:name="UMENG_CHANNEL" android:value="${channel}"" /><fileset dir="" includes="AndroidManifest.xml" /></replaceregexp><property name="out.final.file"location="${apk.dir}/${app_name}${app_version}@${channel}.apk" /><antcall target="clean" /><antcall target="release" /><antcall target="clean" /></target></project>

此文件配置获得打包命令,打包渠道,以及修改文件名,最后打包的过程《完》




-------------------------------------------------------------我是分割线----------------------------------------------------------------------------

二、再讲一下Gradle打包的流程

1、配置好build.gradle,如下

2、studio命令行:
gradlew assembleDebug --打非正式包
gradlew assembleRelease --打正式包
gradlew assembleWandoujiaRelease -打特定渠道

结束!

android {    signingConfigs {        debug {            keyAlias 'your_alias_key'            keyPassword 'your_key_pwd            storePassword 'your_store_pwd'            storeFile file('your_store_key')        }        release {            keyAlias 'your_alias_key'            storeFile file('your_store_key')            if (System.console() != null) {                keyPassword System.console().readLine("\nKey password: ")                storePassword System.console().readLine("\nKeystore password: ")            }        }    }    buildTypes {        debug {            //多余的参数            minifyEnabled false            zipAlignEnabled false            shrinkResources false            signingConfig signingConfigs.debug            // 显示Log            buildConfigField "boolean", "LOG_DEBUG", "true"        }        release {            minifyEnabled true//缩小            zipAlignEnabled true            shrinkResources true//删除无用资源            signingConfig signingConfigs.release            // 显示Log            buildConfigField "boolean", "LOG_DEBUG", "false"            applicationVariants.all { variant ->                variant.outputs.each { output ->                    def outputFile = output.outputFile                    if (outputFile != null && outputFile.name.endsWith('.apk')) {                        // 输出apk名称为apkName_v1.0_wandoujia.apk                        def fileName = "apkName${defaultConfig.versionName}_${variant.productFlavors[0].name}.apk"                        output.outputFile = new File(outputFile.parent, fileName)                    }                }            }            proguardFile 'your_cfg'--例:E:/SorkSpace/branches/studio/proguard.cfg        }    }    productFlavors {        baidu {}        tencent {}      }    productFlavors.all {        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]    }    compileSdkVersion 19    buildToolsVersion '22.0.1'    sourceSets {        main {            manifest.srcFile 'AndroidManifest.xml'            java.srcDirs = ['src']            resources.srcDirs = ['src']            aidl.srcDirs = ['src']            renderscript.srcDirs = ['src']            res.srcDirs = ['res']            assets.srcDirs = ['assets']        }        // Move the tests to tests/java, tests/res, etc...        instrumentTest.setRoot('tests')        // Move the build types to build-types/<type>        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...        // This moves them out of them default location under src/<type>/... which would        // conflict with src/ being used by the main source set.        // Adding new build types or product flavors should be accompanied        // by a similar customization.        debug.setRoot('build-types/debug')        release.setRoot('build-types/release')    }    defaultConfig {        applicationId 'com.chemayi.manager'        versionCode 20        versionName '3.0'        minSdkVersion 10        targetSdkVersion 19        // dex突破65535的限制        multiDexEnabled true        // AndroidManifest.xml 里面UMENG_CHANNEL的value为 ${UMENG_CHANNEL_VALUE}        // manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_name"]    }    dexOptions {        incremental true        javaMaxHeapSize "4g"    }    packagingOptions {        exclude 'META-INF/NOTICE.txt'        exclude 'META-INF/LICENSE.txt'    }    lintOptions {        abortOnError false    }}

配置比Ant简单多了,当然在命令行也可以打包,只不过将gradle换成gradlew即可

其次将Gradle下载后,配置环境将Gradle/bin放入即可。

问题1、https://repo1.maven.org/maven2/com/android/tools/build/gradle/只支持最高2.1.3版本,日期2017.01.16

问题2、2.2.0以下出现,   > org.gradle.api.internal.tasks.DefaultTaskInputs$TaskInputUnionFileCollectio
n cannot be cast to org.gradle.api.internal.file.collections.DefaultConfigurable
FileCollection

因此暂时不使用命令行操作。




-------------------------------------------------------------我是分割线----------------------------------------------------------------------------

三、Python打包

1、安装python软件

2、在项目中放入ChannelUtil.java类,用来获得渠道号

3、打好一个包放在与MultiChannelBuildTool.py同级目录

4、在.py同级目录info下的channel.txt添加渠道号

5、点击MultiChannelBuildTool.py即可

文件目录:

新包:


ChannelUtil.java

package com.blog.util;import java.io.IOException;import java.util.Enumeration;import java.util.zip.ZipEntry;import java.util.zip.ZipFile;import android.content.Context;import android.content.SharedPreferences;import android.content.SharedPreferences.Editor;import android.content.pm.ApplicationInfo;import android.content.pm.PackageManager.NameNotFoundException;import android.preference.PreferenceManager;import android.text.TextUtils;public class ChannelUtil {private static final String CHANNEL_KEY = "yourchannel";private static final String CHANNEL_VERSION_KEY = "yourchannel_version";private static String mChannel;/** * 返回市场。  如果获取失败返回"" * @param context * @return */public static String getChannel(Context context){return getChannel(context, "");}/** * 返回市场。  如果获取失败返回defaultChannel * @param context * @param defaultChannel * @return */public static String getChannel(Context context, String defaultChannel) {//内存中获取if(!TextUtils.isEmpty(mChannel)){return mChannel;}//sp中获取mChannel = getChannelBySharedPreferences(context);if(!TextUtils.isEmpty(mChannel)){return mChannel;}//从apk中获取mChannel = getChannelFromApk(context, CHANNEL_KEY);if(!TextUtils.isEmpty(mChannel)){//保存sp中备用saveChannelBySharedPreferences(context, mChannel);return mChannel;}//全部获取失败return defaultChannel;    }/** * 从apk中获取版本信息 * @param context * @param channelKey * @return */private static String getChannelFromApk(Context context, String channelKey) {//从apk包中获取        ApplicationInfo appinfo = context.getApplicationInfo();        String sourceDir = appinfo.sourceDir;        //默认放在meta-inf/里, 所以需要再拼接一下        String key = "META-INF/" + channelKey;        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(key)) {                    ret = entryName;                    break;                }            }        } catch (IOException e) {            e.printStackTrace();        } finally {            if (zipfile != null) {                try {                    zipfile.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        String[] split = ret.split("_");        String channel = "";        if (split != null && split.length >= 2) {        channel = ret.substring(split[0].length() + 1);        }        return channel;}/** * 本地保存channel & 对应版本号 * @param context * @param channel */private static void saveChannelBySharedPreferences(Context context, String channel){SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);Editor editor = sp.edit();editor.putString(CHANNEL_KEY, channel);editor.putInt(CHANNEL_VERSION_KEY, getVersionCode(context));editor.commit();}/** * 从sp中获取channel * @param context * @return 为空表示获取异常、sp中的值已经失效、sp中没有此值 */private static String getChannelBySharedPreferences(Context context){SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);int currentVersionCode = getVersionCode(context);if(currentVersionCode == -1){//获取错误return "";}int versionCodeSaved = sp.getInt(CHANNEL_VERSION_KEY, -1);if(versionCodeSaved == -1){//本地没有存储的channel对应的版本号//第一次使用  或者 原先存储版本号异常return "";}if(currentVersionCode != versionCodeSaved){return "";}return sp.getString(CHANNEL_KEY, "");}/** * 从包信息中获取版本号 * @param context * @return */private static int getVersionCode(Context context){try{return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;}catch(NameNotFoundException e) {e.printStackTrace();}return -1;}}
MultiChannelBuildTool.py

#!/usr/bin/python# coding=utf-8import zipfileimport shutilimport os# 空文件 便于写入此空文件到apk包中作为channel文件src_empty_file = 'info/yourchannel_.txt'# 创建一个空文件(不存在则创建)f = open(src_empty_file, 'w') f.close()# 获取当前目录中所有的apk源包src_apks = []# python3 : os.listdir()即可,这里使用兼容Python2的os.listdir('.')for file in os.listdir('.'):    if os.path.isfile(file):        extension = os.path.splitext(file)[1][1:]        if extension in 'apk':            src_apks.append(file)# 获取渠道列表channel_file = 'info/channel.txt'f = open(channel_file)lines = f.readlines()f.close()for src_apk in src_apks:    # file name (with extension)    src_apk_file_name = os.path.basename(src_apk)    # 分割文件名与后缀    temp_list = os.path.splitext(src_apk_file_name)    # name without extension    src_apk_name = temp_list[0]    # 后缀名,包含.   例如: ".apk "    src_apk_extension = temp_list[1]        # 创建生成目录,与文件名相关    output_dir = 'output_' + src_apk_name + '/'    # 目录不存在则创建    if not os.path.exists(output_dir):        os.mkdir(output_dir)            # 遍历渠道号并创建对应渠道号的apk文件    for line in lines:        # 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下        target_channel = line.strip()        # 拼接对应渠道号的apk        target_apk = output_dir + src_apk_name + "_" + target_channel + src_apk_extension          # 拷贝建立新apk        shutil.copy(src_apk,  target_apk)        # zip获取新建立的apk文件        zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)        # 初始化渠道信息        empty_channel_file = "META-INF/yourchannel_{channel}".format(channel = target_channel)        # 写入渠道信息        zipped.write(src_empty_file, empty_channel_file)        # 关闭zip流        zipped.close()
channel.txt

        baidu         tencent 


最近Android7.0出现新的签名机制,如果不适配将出现无签名的问题,华为Mate8最先出现,取巧的方式是将该签名机制取消(它是向下兼容的),build.gradle的release设置如下

      release {            // 如果要支持最新版的系统 Android 7.0            // 这一行必须加,否则安装时会提示没有签名            // 作用是只使用旧版签名,禁用V2版签名模式            v2SigningEnabled false
上一组的打包机制是修改META-INF,在它的目录下面,使用python快速复制apk,并添加一个channel文件,用来读取作为项目的渠道名,显然这样做是不够严谨的,一直在关注并期待有新的方式解决这两个问题,那么walle应运而生。

其原理:在signiture block里的ID-VALUE对象添加channel-huawei,这样的键值对,再来读取渠道号即可,避免开发者修改apk造成安全问题,同时开放接口给开发者为自己业务注入新的解决方案,walle就是一例。

操作方式:

1、配置build.gradle

在位于项目的根目录 build.gradle 文件中添加Walle Gradle插件的依赖, 如下:

buildscript {    dependencies {        classpath 'com.meituan.android.walle:plugin:1.0.3'    }}

并在当前App的 build.gradle 文件中apply这个插件,并添加上用于读取渠道号的AAR

apply plugin: 'walle'dependencies {    compile 'com.meituan.android.walle:library:1.0.3'}

2、如何获取渠道信息

在需要渠道等信息时可以通过下面代码进行获取

String channel = WalleChannelReader.getChannel(this.getApplicationContext());

3、如何生成渠道包

生成渠道包的方式是和assemble指令结合,可以通过传入参数决定是否生成渠道包,渠道包的生成目录存放在 build/outputs/apk/

下面是各类用法示例:

  • 生成单个渠道包 ./gradlew clean assembleRelease -PchannelList=meituan
  • 支持 productFlavors ./gradlew clean assembleMeituanRelease -PchannelList=meituan
  • 生成多个渠道包 ./gradlew clean assembleRelease -PchannelList=meituan,dianping
  • 通过渠道配置文件来生成渠道包 ./gradlew clean assembleRelease -PchannelFile=channel

渠道号设置两种方式:

第一种:在项目目录下新建一个channel文件,利用上面第4条通过配置文件生成渠道包

samsungapps #三星hiapkanzhixiaomi # 小米
第二种:直接配合gradle clean assembleRelease 命令使用,采用build.gradle中原始的渠道号方式

   productFlavors {        official {}        baidu {}    }

4、更多用法

插入额外信息

如果想插入除渠道以外的其他信息,请在生成渠道包时使用

./gradlew clean assembleRelease -PchannelList=meituan -PextraInfo=buildtime:20161212,hash:xxxxxxx

extraInfo以key:value形式提供,多个以,分隔。

注意:

  • extraInfo需要搭配channelList或者channelFile使用,plugin不支持只写入extraInfo。
  • extraInfo 不要出现以channel为key的情况

而对应的渠道信息获取方式如下:

ChannelInfo channelInfo= WalleChannelReader.getChannelInfo(this.getApplicationContext());if (channelInfo != null) {   String channel = channelInfo.getChannel();   Map<String, String> extraInfo = channelInfo.getExtraInfo();}// 或者也可以直接根据key获取String value = WalleChannelReader.get(context, "buildtime");

而对应的渠道信息获取方式如下:

应用签名方案APK Signature Scheme v2原理:http://tech.meituan.com/android-apk-v2-signature-scheme.html

官方地址:https://github.com/Meituan-Dianping/walle



1 0
原创粉丝点击