使用Ant在mac os下实现多渠道打包

背景

于还在使用Eclipse做为开发工具的同学而言:
由于各种原因,还有部分公司使用Eclipse作为Android开发工具,版本要上线的时候,要上传渠道包到国内主流应用平台,如:应用宝,安智市场,百度的三个市场,360,华为等等,但是Eclipse不提供多渠道打包的工具。如果重复使用Eclipse手动打包,更改AndroidManifest中的UmengChannel,再签名导出,不仅降低了线率,还容易出错,比如你漏改了清单文件中的参数,或者是打包完成之后你的APK文件名没改。于是就有人提出使用ant来实现自动化。
于我自己而言:
接触渠道包的概念很久了,从十一月份开始准备相关工作。虽然已经有大神在网上贴出了各自实现多渠道打包的方法,但是由于每个项目都有特殊性,所以不能直接照搬,开发一套适合自己项目的多渠道打包工具才是王道。由于各种原因(自己太懒),学习相关知识断断续续。越是半途而废,越是想要搞清楚,自动打包的实现原理以及相关的技术。最后实现出来,不仅是为了公司项目,对自己也是一个交代。一直到昨天,终于被我整出来了,当然很多是建立在前辈的基础之上。今天,我就拿来炫一下,有什么不妥的,请留言。


多渠道打包的学习步骤

下面是我学习自动打包的几个步骤,因为自动打包相对平时编码来说,需要的知识面更加宽广。内在联系也不是很紧密。所以,如果想要彻底搞清楚自动打包的同学,还请仔细看,然后找到资料学习之。

Android和Java常用命令的使用

  • aapt命令 全称:Android Asset Packaging Tool。用于打包资源文件和清单文件。值得注意的是:在打包资源文件得时候,不仅要打包本项目的资源文件,还要打包依赖项目的资源文件。这是打包成功的必要保证。
  • javac命令 APK中不仅包含有资源文件,还有我们用java写的逻辑,这些逻辑的实现,需要依靠dalvik虚拟机运行dex文件,而dex文件又是通过class文件转化,所以我们需要将源文件即.java文件编译成class文件,这个时候需要使用到javac命令。值得注意的是:我们编译的时候要把本项目的源代码以及第三方包的源代码进行编译。
  • dx命令 :由javac命令,我们知道,dalvik虚拟机最终运行的是dex文件,所以,我们就要通过dx命令将class文件转换为dex文件。这个时候用到dx命令。着重强调:在编译源代码的时候,我们一般喜欢使用高版本的jdk去编译,但是Java不支持java8。如果在打包的时候,出现报错类似:caffeebabe……magic….等文字的时候,基本上可以推论是你的jdk版本不适合当前得安卓版本,不要问我为什么知道的,正好在实现自动打包的前一天,我看了java在编译成class文件时的细节:在class文件内部,有一个可以标识符,用于判断这个class文件是否是可用,为了维护安全性,在每个class文件的头部,都有一个成为魔数(magic)的标识符,这个标识符用十六进制编码翻译过来就是caffeebabe(咖啡宝贝)。正好和上文的报错相吻合。
  • apkbuilder命令 这个命令在Android3.0之后已经被剔除,通过网上资料显示,这个命令是对com.android.sdklib.build.ApkBuilderMain类的封装,我们就可以使用ant来利用这个类实现同样的功能。这个命令的主要功能是把资源文件和dex,以及项目使用的so包打包成未签名的apk。注意,如果你的项目中包含了so,一定要打进去,否则即使打包成功,但是项目运行到相关位置会闪退。
  • jarsigner命令 这个是java命令,这里用于对未签名的apk进行签名。

APK打包的流程

下面对于Android整个打包的流程进行描述。

  1. 生成用于主应用的R.java,这里用到aapt命令。
  2. 生成用于库应用的R.java,也是用到aapt命令
  3. 编译所有java文件为class文件,用到javac命令
  4. 打包class文件和jar包为classes.dex,用到dx命令
  5. 打包assets和res资源为资源压缩包(如res.zip,名字可以自己定义),用到aapt命令。
  6. 组合classes.dex和res.zip,以及so文件生成未签名的APK。用到apkBuilder命令。
  7. 生成有签名的APK,用到 jarsigner命令。

    再次,借用网络上一张图来描述:
    使用Ant在mac os下实现多渠道打包_第1张图片


Ant基本知识

ant 是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。在实际软件开发中,有很多地方可以用到ant。这里,我们使用ant实现自动编译,打渠道包。关于ant的使用可以参考这里以及这里。Ant运行时需要一个XML文件(构建文件)。 Ant通过调用target树,就可以执行各种task。每个task实现了特定接口对象。 这里,我们就要学会如何写这个xml文件。ant读取的xml文件通常默认命名为 build.xml文件。下面对其结构做一些简要介绍,具体的可以参考上述两个连接。

  • project 元素 project元素是 Ant 构件文件的根元素, Ant 构建文件至少应该包含一个 project 元素,否则会发生错误。
  • target元素 target元素是一组操作得集合,包含了若干个Ant的原子操作。其中比较重要的参数是:depends,用于指定该target的依赖target。
  • task元素 该元素是ant执行的最小单位,用来说明一个操作。

以上就是build.xml的结构,清楚结构之后,我们需要通过一些常用的task去组合我们要实现的逻辑。
在学习ant的时候,我感觉ant有点类似C语言。它可以通过property来指定属性,类似C语言的全局变量。而taget类似C语言中的函数,task类似C语言的一行代码。有了这个认识,我们对ant有一个不错的认识。


sed命令

sed是stream editor的简称,也就是流编辑器。通过这个命令,我们可以方便的对文件进行操作,比如删除文本,修改文本,增加文本等。
在 sed中,我们使用最多的是 s命令,它可以通过正则表达式对文本进行灵活的修改。想要深入学习的点击这里。

自动打包的实现

我们通过APK的打包过程的描述,将常用命令进行封装。达到我们的业务需要。现在拆分如下:

1)新建需要使用的文件系统。注意:新建的文件和目录不要与自生IDE的文件和目录重复,这样就会出现冲突。导致IDE报错。xml代码如下:

   <target name="init">
        <echo>start initing ... echo>

        <mkdir dir="ant/out" />

        <echo> create out dir successecho>

        <delete>
            <fileset dir="ant/out">fileset>
        delete>

        <mkdir dir="ant/gen" />
        <echo> create gen dir successecho>
        <delete>
            <fileset dir="ant/gen">fileset>
        delete>
        <mkdir dir="ant/bin/classes" />
        <echo> create classes dir successecho> 
        <delete>
            <fileset dir="ant/bin/classes">fileset>
        delete> 
        <mkdir dir="ant/build/${apk-version}" />
        <echo> create latest dir successecho> 
        <echo>finish initing. echo>
    target>

2)打包本项目的R文件,以及依赖项目的R文件

  <target name="main" depends="init">
        <echo>generating R.java for project to dir gen (using aapt) ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-m" />
            <arg value="-J" />
            <arg value="gen" />
            <arg value="-M" />
            <arg value="AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../pull_library/res" />
            <arg value="-S" />
            <arg value="../wheel/res" />
            <arg value="-I" />
            <arg value="${android-jar}" />
            <arg value="--auto-add-overlay" />
        exec>

        <echo>generating R.java for library from pull_library to dir gen (using aapt) ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-m" />
            <arg value="--non-constant-id" />
            <arg value="--auto-add-overlay" />
            <arg value="-J" />
            <arg value="gen" />
            <arg value="-M" />
            <arg value="../pull_library/AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../pull_library/res" />
            <arg value="-I" />
            <arg value="${android-jar}" />
        exec>

        <echo>generating R.java for library from wheel to dir gen (using aapt) ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-m" />
            <arg value="--non-constant-id" />
            <arg value="--auto-add-overlay" />
            <arg value="-J" />
            <arg value="gen" />
            <arg value="-M" />
            <arg value="../wheel/AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../wheel/res" />
            <arg value="-I" />
            <arg value="${android-jar}" />
        exec>

3)编译源代码

  <path id="project.libs">
            <fileset dir="libs">
                <include name="*.jar" />
            fileset>
        path>
        <echo>compiling java files to class files (include R.java, library and the third-party jars) ... echo>
        <javac destdir="ant/bin/classes" target="1.6" bootclasspath="${android-jar}">
            <src path="../pull_library/src" />
            <src path="../wheel/src" />
            <src path="src" />
            <src path="gen" />
            <classpath refid="project.libs" />
        javac>

4)class 转dex

        packaging class files (include the third-party jars) to calsses.dex ... 
        "/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/dx">
            "--dex" />
            "--output=ant/out/classes.dex" />
            "ant/bin/classes" />
            "libs" />
        

5)打包资源文件

  <echo>packaging resource (include res, assets, AndroidManifest.xml, etc.) to res.zip ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-f" />
            <arg value="-M" />
            <arg value="AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../pull_library/res" />
            <arg value="-S" />
            <arg value="../wheel/res" />
            <arg value="-A" />
            <arg value="assets" />
            <arg value="-I" />
            <arg value="${android-jar}" />
            <arg value="-F" />
            <arg value="ant/out/res.zip" />
            <arg value="--auto-add-overlay" />
        exec>

6) 生成未签名的apk

  <java classpath="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/tools/lib/sdklib.jar" classname="com.android.sdklib.build.ApkBuilderMain">
            <arg value="ant/out/unsigned.apk" />
            <arg value="-u" />
            <arg value="-z" />
            <arg value="ant/out/res.zip" />
            <arg value="-f" />
            <arg value="ant/out/classes.dex" />
            <arg value="-nf" />
            <arg value="libs" />
        java>

7) 签名apk

  <echo>signing the unsigned apk to final product apk ... echo>
        <exec executable="jarsigner">
            <arg value="-keystore" />
            <arg value="/Users/yuanyang/Desktop/signature/xxxx" />
            <arg value="-storepass" />
            <arg value="xxxxxx" />
            <arg value="-keypass" />
            <arg value="xxxxxx" />
            <arg value="-signedjar" />
            <arg value="ant/build/${apk-version}/${apk-name}_${apk-version}_${apk-market}.apk" />
            <arg value="ant/out/unsigned.apk" />
            <arg value="xxxxxx" />
        exec>

整个文件为:


<project default="main" basedir=".">

    <property name="apk-name" value="product" />
    <property name="apk-version" value="latest" />
    <property name="apk-market" value="dev" />
    <property name="ant" value="ant" />

    <property name="android-jar" value="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/platforms/android-21/android.jar" />

    <target name="init">
        <echo>start initing ... echo>

        <mkdir dir="ant/out" />

        <echo> create out dir successecho>

        <delete>
            <fileset dir="ant/out">fileset>
        delete>

        <mkdir dir="ant/gen" />
        <echo> create gen dir successecho>

        <delete>
            <fileset dir="ant/gen">fileset>
        delete>

        <mkdir dir="ant/bin/classes" />
        <echo> create classes dir successecho>

        <delete>
            <fileset dir="ant/bin/classes">fileset>
        delete>

        <mkdir dir="ant/build/${apk-version}" />
        <echo> create latest dir successecho>


        <echo>finish initing. echo>
    target>

    <target name="main" depends="init">
        <echo>generating R.java for project to dir gen (using aapt) ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-m" />
            <arg value="-J" />
            <arg value="gen" />
            <arg value="-M" />
            <arg value="AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../pull_library/res" />
            <arg value="-S" />
            <arg value="../wheel/res" />
            <arg value="-I" />
            <arg value="${android-jar}" />
            <arg value="--auto-add-overlay" />
        exec>

        <echo>generating R.java for library from pull_library to dir gen (using aapt) ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-m" />
            <arg value="--non-constant-id" />
            <arg value="--auto-add-overlay" />
            <arg value="-J" />
            <arg value="gen" />
            <arg value="-M" />
            <arg value="../pull_library/AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../pull_library/res" />
            <arg value="-I" />
            <arg value="${android-jar}" />
        exec>

        <echo>generating R.java for library from wheel to dir gen (using aapt) ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-m" />
            <arg value="--non-constant-id" />
            <arg value="--auto-add-overlay" />
            <arg value="-J" />
            <arg value="gen" />
            <arg value="-M" />
            <arg value="../wheel/AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../wheel/res" />
            <arg value="-I" />
            <arg value="${android-jar}" />
        exec>

        <path id="project.libs">
            <fileset dir="libs">
                <include name="*.jar" />
            fileset>
        path>
        <echo>compiling java files to class files (include R.java, library and the third-party jars) ... echo>
        <javac destdir="ant/bin/classes" target="1.6" bootclasspath="${android-jar}">
            <src path="../pull_library/src" />
            <src path="../wheel/src" />
            <src path="src" />
            <src path="gen" />
            <classpath refid="project.libs" />
        javac>

        <echo>packaging class files (include the third-party jars) to calsses.dex ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/dx">
            <arg value="--dex" />
            <arg value="--output=ant/out/classes.dex" />
            <arg value="ant/bin/classes" />
            <arg value="libs" />
        exec>

        <echo>packaging resource (include res, assets, AndroidManifest.xml, etc.) to res.zip ... echo>
        <exec executable="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/build-tools/android-5.1/aapt">
            <arg value="package" />
            <arg value="-f" />
            <arg value="-M" />
            <arg value="AndroidManifest.xml" />
            <arg value="-S" />
            <arg value="res" />
            <arg value="-S" />
            <arg value="../pull_library/res" />
            <arg value="-S" />
            <arg value="../wheel/res" />
            <arg value="-A" />
            <arg value="assets" />
            <arg value="-I" />
            <arg value="${android-jar}" />
            <arg value="-F" />
            <arg value="ant/out/res.zip" />
            <arg value="--auto-add-overlay" />
        exec>

        <java classpath="/Users/yuanyang/Downloads/adt-bundle-mac-x86_64-20131030/sdk/tools/lib/sdklib.jar" classname="com.android.sdklib.build.ApkBuilderMain">
            <arg value="ant/out/unsigned.apk" />
            <arg value="-u" />
            <arg value="-z" />
            <arg value="ant/out/res.zip" />
            <arg value="-f" />
            <arg value="ant/out/classes.dex" />
            <arg value="-nf" />
            <arg value="libs" />
        java>

        <echo>signing the unsigned apk to final product apk ... echo>
        <exec executable="jarsigner">
            <arg value="-keystore" />
            <arg value="/Users/yuanyang/Desktop/signature/随时领路" />
            <arg value="-storepass" />
            <arg value="111111" />
            <arg value="-keypass" />
            <arg value="111111" />
            <arg value="-signedjar" />
            <arg value="ant/build/${apk-version}/${apk-name}_${apk-version}_${apk-market}.apk" />
            <arg value="ant/out/unsigned.apk" />
            <arg value="yuanyang" />
        exec>

        <echo>done.echo>
    target>
project>

在命名行将目录切换到build.xml目录,运行ant,运行截图:
使用Ant在mac os下实现多渠道打包_第2张图片

下面,我们到apk-version下面去查看刚刚生成apk。使用Ant在mac os下实现多渠道打包_第3张图片

下面,我们使用ClassyShark来验证我们生成的包的内部结构:
使用Ant在mac os下实现多渠道打包_第4张图片


多渠道打包的实现

上一步实现了自动打包,但是对于打渠道包还能没有实现。这里借助于osx平台下的sed脚本进行循环打包。我们在项目里面新建 build.sh文件,加上自己的渠道名,实现渠道打包,脚本代码:

#定义市场列表,以空格分割
markets="Tecent Anzhi Baidu Huawei Xiaomi Lenovel"
#循环市场列表,分别传值给各个脚本
for market in $markets
do
    echo packaging leroda_app_android_$market.apk ...
    #替换AndroidManifest.xml中Channel值(针对友盟,其他同理)
    sed -i '' "s/\(android:value=\)\"\(.*\)\"\( android:name=\"UMENG_CHANNEL\"\)/\1\"$market\"\3/g" AndroidManifest.xml
    #编译对应的版本
    ant -Dapk-name=floworld -Dapk-version=1.0 -Dapk-market=$market
done

。在mac命令行下运行 /.build.sh 运行该文件,实现自动打包。

总结:

  • 在学习Android的时候,要把眼界放宽,学习一下相关知识,有利于加深我们的理解。
  • 虽说平时不经常使用Android的命令,但是这是基础,有助于提高对Android的理解。
  • 在写博客的最后发现,这里没有做混淆。下一步将混淆功能加进去。

相关链接:

  • 主要讲实现工程,自己的东西很多一部分基于这个改装
  • sed命令详解
  • Ant使用指南
  • Ant使用手册
  • 手动打包
  • apkBuilder命令的替换方案

感谢

最后感谢工作中给予我帮助的同事。

你可能感兴趣的:(今日总结,android开发经验,安卓开发实战)