Android 多渠道打包详细教程(一)-ant

小菜鸟战斗机 2018-3-28 193

最近做的项目快上线了,所以就把多渠道打包的一些工具研究研究,之前用的最多的还是ant,androidstudio出来了之后,又可以使用gradle来进行多渠道打包,所以准备一个一个研究,一个一个写。本篇先讲解ant的使用,学习之初在网上搜索了各种方法,都是把所以的build的任务都定义出来的,但是我自己使用ant update过项目之后,然后去看sdk/tools/ant目录下,有一个build.xml,里面各种任务定义的很清楚也很规范,查询了之后知道好像是sdk不知道更新到哪一个版本之后自带了这个玩意,就省去了很多繁琐的任务定义。ant我其实不会,但是研究了一周左右也有一些了解,会了基本的语法和使用,本篇只讲我用到过且使用正确无误的的语法及自己的理解,可能会有偏差,其他的不多讲,好了,下面开始:

一、ant简介

Ant是一种基于Java的build工具。Ant构建文件是XML文件。每个构建文件定义一个唯一的项目(Project元素)。当开始一个新的项目时,首先应该编写Ant构建文件。构建文件定义了构建过程,并被团队开发中每个人使用。Ant构建文件默认命名为build.xml,也可以取其他的名字。只不过在运行的时候把这个命名当作参数传给Ant。 
注意默认的构建文件为build.xml。ant脚本语言,一行一行的去执行代码。

1、基本语法

 <echo level="info">mutilchannelsbuild</echo>  在控制台打日志1
<property /> 可以定义属性赋值,引入文件,引入系统变量

<property file="ant.properties" /> 导入ant.properties
<property environment="env" />引入系统变量并命名为env
<property name="voip.dir" location="../LinkUsLibrary"/>定义属性赋值12345
<import file="custom_rules.xml" optional="true" /> 导入另外一个xml,这个xml中可以和build.xml的功能类似,感觉相当于merge1
 <taskdef 定义一个ant任务,可以引用class,jar例如循环打包要用到的ant-contrib
    <taskdef resource="net/sf/antcontrib/antcontrib.properties" >
        <classpath>
            <pathelement location="${ant.ANT_HOME}/lib/ant-contrib-1.0b3.jar" />
        </classpath>
    </taskdef>1234567
<tstamp> 时间戳,可以format,并属性命名,例如:    <tstamp>
        <format            pattern="yyyyMMddhhmm"
            property="pktime"  属性名称            unit="hour" />
    </tstamp>12345678
在ant中,引用属性变量使用${name},例如:<mkdir dir="${apk.dir}" />

<mkdir dir="${apk.dir}" />  mkdir是创建文件夹123
<xmlproperty 也是导入一个xml,然后可以使用内部的属性,以该xml的节点的name命名,例如:

<xmlproperty file="AndroidManifest.xml" collapseAttributes="true" />
要读取versionname就可以使用${manifest.android:versionName}1234
<antcall target="-release-close" /> 调用某一个任务,如果要传参数,可以在内部加上 <param,例如:         <antcall target="-presetup" >
            <param                name="logcat"
                value="false" />
             <param                name="server_url"
                value="${rel.server.url}" />
        </antcall>123456789
<copy,文件copy,例如:                <copy todir="${voip.dir.lib}"> 目标路径                    <fileset dir="${voip.dir.so.closed}/"> 其实路径                         <include name="/*" /> 删除过滤                    </fileset>
                </copy>123456
<replaceregexp 通过正则表达式来替换文件中的字符,可以用来替换渠道号,log开关变量,例如:

        <replaceregexp
            byline="false"
            encoding="UTF-8"
            flags="g" >

            <regexp pattern="private final boolean enablelog = true" />  要去匹配的字符串            <substitution expression="private final boolean enablelog = ${logcat}" />  要替换为的字符串            <fileset                dir="./"
                includes="${source.dir}/com/xxxx/xxx/xxxApplication.java" /> 指定文件        </replaceregexp>12345678910111213141516
if else,这个就不用说了,内部嵌套任务        <if condition="${logcat}">
            <then>
                <echo level="info">open logcat</echo>
                <copy todir="${voip.dir.lib}">
                    <fileset dir="${voip.dir.so.open}/">
                         <include name="/*" />
                    </fileset>
                </copy>
            </then>
            <else>
                <echo level="info">close logcat</echo>
                <copy todir="${voip.dir.lib}">
                    <fileset dir="${voip.dir.so.closed}/">
                         <include name="/*" />
                    </fileset>
                </copy>
            </else>
        </if>12345678910111213141516171819

以上是我目前使用到的一些语法,当然不止这些,有兴趣的可以去研究一下sdk目录下的build.xml,里面有更多更详细的使用方法,我后续的自定义task有的就是参考它所写。

二、具体到android build的使用

1、安装ant,jdk,android sdk,并设置环境变量

ant:

ANT_HOME = 安装路径
path += %ANT_HOME%\bin;%ANT_HOME%\lib;12

配置完成之后,cmd执行ant -version,如果出现类似以下内容就为成功

Apache Ant(TM) version 1.9.4 compiled on April 29 20141

也可以看出我使用的版本是1.9.4

jdk:这个不会的可自行百度

android sdk:

ANDROID_SDK_HOME = 安装路径
path += %ANDROID_SDK_HOME%\tools;%ANDROID_SDK_HOME%\platform-tools;12

配置sdk的路径可以让你方便的使用一些命令,例如:adb相关的命令,生成签名文件,draw9patch,hierarchyviewer,monkey等等

2、使用ant命令更新工程

在自己的工程目录下使用ant 命令update project来给工程生成build.xml

1)单个project使用

android update project --name <project_name> --target <target_ID>--path <path_to_your_project>1

来生成build.xml 
例如:

android update project --name MyProject --target android-22 --path ./1

2)项目主工程依赖多个lib的工程

在lib工程下使用

android update lib-project -p ./1

生成build.xml 
然后在自己的主工程使用

android update project --name <project_name> --target <target_ID>--path <path_to_your_project> --subprojects1

命令来生成build.xml 
例如:

android update project --name MyProject --target android-22 --path ./ --subprojects1

至此,已经可以使用ant debug来打debug的apk了,但是还不能打带签名的包(默认输入路径为主工程目录的bin目录下)

3、使用ant打出正式带签名的apk

1)生成签名文件

先使用Eclipse导出一个keystore,签名文件存放在地方或者直接放在主工程目录下,并记住设置的密码和anlias的名字 
或者使用命令来生成

keytool -genkey -alias aliasname -keyalg RSA -validity 20000 -keystore filename.keystore1

2)设置默认配置

然后在主工程目录下新建ant.properties,内如如下:

# keystore的路径,必须使用正斜杠key.store="E:/xxx/me.key"# 如果签名文件 在主工程目录下,key.store直接=文件名,例如key.store=test.keystore# keystore的密码key.store.password=testpassword123# alias名 key.alias=me# alias密码key.alias.password=testpassword123123456789

至此,可以使用ant release打出带签名的apk了(默认输入路径为主工程目录的bin目录下)

4、说明

下面要说明一下,为什么要这么配置。 
在主工程目录下update之后,生成了一个build.xml,内容如下(删除了注释):

<?xml version="1.0" encoding="UTF-8"?><project name="MyProject" default="help">
    <property file="local.properties" />

    <property file="ant.properties" /> 这里引入了ant.properties,所以在ant.properties中定义了签名所用到的一些属性,而为什么必须要这样来定义,就需要看sdk目录下的build.xml,这个文件下使用了这些对应的属性值,所以在这里定义之后,后续的task中可以直接用这些值    <property environment="env" />
    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
        <isset property="env.ANDROID_HOME" />
    </condition>
    <!-- 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 file="custom_rules.xml" optional="true" /> 可以在custom_rules.xml中自定义我们的任务    <import file="${sdk.dir}/tools/ant/build.xml" /></project>1234567891011121314151617181920

那接下来就可以自定义一些task了

三、自定义task

由于我的项目每天都要打包给测试部门,而打包又需要分别打包 正式服务器log open版本,正式服务器log close版本,test服务器版本,之前每次打包我都是用Eclipse打包,每打一个版本出来就去手动修改一下相关的值,然后再进行下一个,不说修改值的麻烦了,就每次输入密码输入我都要崩溃,而且到后来Eclipse只要一打包就必崩溃,wtf,而这也是促使我必须把ant打包搞明白的动力之一。 
结合前面所知道的,ant是脚本工具,它是一步一步来执行任务,再参考sdk中的build.xml,这一个dailybuild的任务要分为两个步骤: 
一、build之前的配置 
二、调用android sdk给定义好的任务 clean ,release,这两个任务调用很简单,直接使用antcall即可 
那对于步骤一而言,配置的任务就是服务器地址和log开关的配置 
首先分析这三个版本的区别,正式服务器和测试服务器之分,log开关之分,映射到ant具体的任务的话,就有一下几点: 
task1、修改服务器地址的任务 
task2、配置log开关的任务 
而任务2,配置log开关的任务又要拆分为: 
task2.1、修改java code中log开关的boolean变量 
task2.2、拷贝so任务,依赖的Library中的.so有开关log版本之分,所以在Library目录下分别有两个目录libs-log-closed和libs-log-opened,里面分别放着相应的.so,log开关不同的版本需要使用不同的.so 
OK,任务拆分完了就简单了,直接去写脚本吧,可以像写代码一样的,先写自己要什么任务,再去写具体的实现过程。 
那在这之前我们要在ant.properties定义一些我们要用的值:

apk.dir=apks
test.server.url=test.xx.xxxx.cn/vision/app/v1
rel.server.url=xxx.phone.xxx.com/app/v1123

然后开始顺着自己的任务分解去写具体的脚本代码 
1)dailybuild

    <!-- 定义一个每日build的任务 ,里面分别调用三个不同的build任务-->
    <target name="dailybuild"  >
        <antcall target="-release-close" />
        <antcall target="-release-open" />
        <antcall target="-test" />
        <!-- build任务完成之后就回复到开发状态 -->
        <antcall target="-reset_to_devstatus" />
    </target>12345678

2)各个子任务

    <!-- 每日打包任务-release线log close的apk -->
    <target name="-release-close" >
        <echo level="info">-release-close</echo>
        <!-- 预处理 -->
        <antcall target="-presetup" >
            <param                name="logcat"
                value="false" />
             <param                name="server_url"
                value="${rel.server.url}" />
        </antcall>
        <!-- 定义输出apk文件路径 -->
        <property            name="out.final.file"
            location="${apk.dir}/${ant.project.name}_${pktime}_v${manifest.android:versionName}_release_logcat_closed.apk" />
        <echo level="info">out.final.file:${out.final.file}</echo>
        <!-- 然后调用android定义的clean和release,可以参考sdk中的定义 -->
        <antcall target="clean" />
        <antcall target="release" />
    </target>
    <!-- 每日打包任务-release线log open的apk -->
    <target name="-release-open" >
        <echo level="info">-release-open</echo>
        <antcall target="-presetup" >
            <param                name="logcat"
                value="true" />
             <param                name="server_url"
                value="${rel.server.url}" />
        </antcall>
        <property            name="out.final.file"
            location="${apk.dir}/${ant.project.name}_${pktime}_v${manifest.android:versionName}_release_logcat_open.apk" />
        <echo level="info">out.final.file:${out.final.file}</echo>
        <antcall target="clean" />
        <antcall target="release" />
    </target>
    <!-- 每日打包任务-test线log open的apk -->
    <target name="-test" >
        <echo level="info">-test</echo>
        <antcall target="-presetup" >
            <param                name="logcat"
                value="true" />
            <param                name="server_url"
                value="${test.server.url}" />
        </antcall>
        <property            name="out.final.file"
         location="${apk.dir}/${ant.project.name}_${pktime}_v${manifest.android:versionName}_test_logcat_open.apk" />
         <echo level="info">out.final.file:${out.final.file}</echo>
        <antcall target="clean" />
        <antcall target="release" />
    </target>123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657

3)预处理配置任务

    <!-- 预处理任务,depends的有:拷贝so任务、设置log开关的任务,设置服务器地址的任务 -->
    <target name="-presetup" depends="-cpsobylogcat,-setlogcat,-seturl">
        <echo level="info">-presetup</echo>
        <echo level="info">logcat: ${logcat}</echo>
        <echo level="info">server_url: ${server_url}</echo>
    </target>123456

4)具体的配置任务的过程

    <!--拷贝so任务,依赖的Library中的.so有开关log版本之分,所以如果log开关不同的版本需要使用不同的.so -->
    <target name="-cpsobylogcat" >
        <echo level="info">-cpso</echo>
        <echo level="info">logcat: ${logcat}</echo>
        <property name="voip.dir" location="../XXLibrary"/>
        <property name="voip.dir.lib" location="../XXLibrary/libs/armeabi"/>
        <property name="voip.dir.so.closed" location="${voip.dir}/libs-log-closed"/>
        <property name="voip.dir.so.open" location="${voip.dir}/libs-log-opened"/>
        <echo level="info">${voip.dir.lib}</echo>
        <echo level="info">${voip.dir.so.closed}</echo>
        <echo level="info">${voip.dir.so.open}</echo>
        <!-- 删除lib下.so -->
        <delete includeEmptyDirs="true" >
            <fileset                dir="${voip.dir.lib}"
                includes="*.so" />
        </delete>
        <!-- 然后根据log开关,拷贝不同的.so -->
        <if condition="${logcat}">
            <then>
                <echo level="info">open logcat</echo>
                <copy todir="${voip.dir.lib}">
                    <fileset dir="${voip.dir.so.open}/">
                         <include name="/*" />
                    </fileset>
                </copy>
            </then>
            <else>
                <echo level="info">close logcat</echo>
                <copy todir="${voip.dir.lib}">
                    <fileset dir="${voip.dir.so.closed}/">
                         <include name="/*" />
                    </fileset>
                </copy>
            </else>
        </if>
    </target>
    <!-- 设置log任务,log开关是在XXApplication.java中定义的一个常量,所以需要通过正则去替换这个常量 -->
    <target name="-setlogcat" >
        <echo level="info">-setlogcat</echo>
        <echo level="info">logcat: ${logcat}</echo>
        <!-- 替换这个常量 -->
        <replaceregexp            byline="false"
            encoding="UTF-8"
            flags="g" >

            <regexp pattern="private final boolean enablelog = (.*);" />

            <substitution expression="private final boolean enablelog = ${logcat};" />

            <fileset                dir="./"
                includes="${source.dir}/com/xx/xxx/XXApplication.java" />
        </replaceregexp>
    </target>
    <!-- 设置服务器地址 -->
    <target name="-seturl" >
        <echo level="info">-seturl</echo>

        <replaceregexp            byline="false"
            encoding="UTF-8"
            flags="g" >

            <regexp pattern="String BASE_HOST = &quot;(.*)&quot;" />

            <substitution expression="String BASE_HOST = &quot;${server_url}&quot;" />

            <fileset                dir="./"
                includes="${source.dir}/com/xx/xxx/UrlConstant.java" />
        </replaceregexp>
    </target>1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374

5)reset任务

   <!-- build任务完成之后就回复到开发状态 -->
    <target name="-reset_to_devstatus" >
        <echo level="info">-reset_to_devstatus</echo>
        <antcall target="-presetup" >
            <param                name="logcat"
                value="true" />
            <param                name="server_url"
                value="${test.server.url}" />
        </antcall>
    </target>123456789101112

至此,我就可以直接在cmd或者Eclipse里直接执行任务ant dailybuild,然后就会自动的把三个包都打出来,并以不同的命名放入我指定的文件夹内。

四、多渠道打包

自定义task如果搞明白了之后,那这个多渠道打包也就不难了,其实就是要读取一些渠道号,然后循环的利用不同的渠道号去build一个apk出来,而每次build之前呢,要把manifest中的渠道号给换掉。 
那我们先去到ant.properties中配置下我们的渠道号。

market_channels=Google,Gfan,AnZhi1

ant自身并不支持循环打包,但是可以加入lib让他支持,就是ant-contlib这个扩展包,ant-contlib这个可以在网上下载到,下完了之后,放入ant安装目录下的lib目录下。 
然后要了解一下语法。

    <!-- 引用ant-contlib这个扩展包,声明一下 -->
    <taskdef resource="net/sf/antcontrib/antcontrib.properties" >
        <classpath>
            <pathelement location="${ant.ANT_HOME}/lib/ant-contrib-1.0b3.jar" />
        </classpath>
    </taskdef>for循环  <foreach            delimiter=","
            list="${market_channels}" 读取在ant.properties中定义的渠道号market_channels,形成一个集合,供for循环使用            param="channel"   每一次循环都会去market_channels中读取一个渠道号,作为给-modifymanifest-build传递的参数            target="-modifymanifest-build" > 调用 -modifymanifest-build任务,-modifymanifest-build任务就是每一次for循环执行的单个build任务,在这个任务中,可以按照上面的分析来执行任务        </foreach>12345678910111213

OK,那下面开始写脚本。

    <!-- ant-contlib这个扩展包是用来循环打包,多渠道打包的时候会用到 -->
    <!-- 引用ant-contlib这个扩展包,声明一下 -->
    <taskdef resource="net/sf/antcontrib/antcontrib.properties" >
        <classpath>
            <pathelement location="${ant.ANT_HOME}/lib/ant-contrib-1.0b3.jar" />
        </classpath>
    </taskdef>1234567
    <!-- 定义一个每日build的任务 ,里面分别调用三个不同的build任务-->
    <target name="dailybuild"  >
        <antcall target="-release-close" />
        <antcall target="-release-open" />
        <antcall target="-test" />
        <!-- build任务完成之后就回复到开发状态 -->
        <antcall target="-reset_to_devstatus" />
    </target>
    <!-- 定义多渠道打包的任务 -->
    <target name="mutilchannelsbuild"  >
        <echo level="info">mutilchannelsbuild</echo>
        <!-- 先调用预处理的任务,并带两个参数-->
        <antcall target="-presetup" >
            <param                name="logcat"
                value="false" />
             <param                name="server_url"
                value="${rel.server.url}" />
        </antcall>
        <!-- 预处理完成之后,调用循环渠道打包任务 -->
        <antcall target="-foreach-modifymanifest-build" />
        <!-- build任务完成之后就回复到开发状态 -->
        <antcall target="-reset_to_devstatus" />
    </target>
    <!-- 定义循环渠道打包任务 -->
    <target name="-foreach-modifymanifest-build" >
        <!-- 循环调用渠道打包任务,并传入channel参数 -->
        <foreach            delimiter=","
            list="${market_channels}"
            param="channel"
            target="-modifymanifest-build" >
        </foreach>
    </target>
    <target name="-modifymanifest-build" >
        <!-- 先替换channel -->
        <replaceregexp            byline="false"
            encoding="UTF-8"
            flags="g" ><!-- INSTALL_CHANNEL我的渠道号对应的name -->
            <regexp pattern="android:value=&quot;(.*)&quot; android:name=&quot;INSTALL_CHANNEL&quot;" />
            <substitution expression="android:value=&quot;${channel}&quot; android:name=&quot;INSTALL_CHANNEL&quot;" />
            <fileset                dir=""
                includes="AndroidManifest.xml" />
        </replaceregexp>
        <!-- 定义输出apk文件路径 -->
        <property            name="out.final.file"
            location="${apk.dir}/${ant.project.name}_${pktime}_v${manifest.android:versionName}_${channel}_release.apk" />
        <!-- 然后调用android定义的clean和release,可以参考sdk中的定义 -->
        <antcall target="clean" />
        <antcall target="release" />
    </target>1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556

至此,就可以在cmd或者Eclipse里直接执行任务ant dailybuild,然后就会自动的根据定义的渠道号,把所以的渠道包都打出来,并以不同的命名放入我指定的文件夹内。

以上就是我这一周以来对ant的研究,后续会继续对as的gradle进行研究。 
另外,ant.properties和custom_rules.xml写完一次,下次换别的项目用的的时候拿出来改吧改吧就OK了。

五、错误总结

过程中也遇到了各种错误,总结下来就几类吧: 
1、命名不规范,比如apk的名字里面有各种奇怪的符号 
2、自己调用的属性不存在 
3、调用的task方法名字不对 
4、语法使用错误 
5、定义要操作的文件不存在 
以上几种错误,每次build失败之后,ant打出的日志会提示,除了语法错误需要自己网上查询,其他的细心的检查都可以慢慢解决的,所以在开始阶段,可以尽量多多使用echo来打打log来看一下自己的执行 
6、要注意sdk\tools\ant\build.xml中的

<property name="java.target" value="1.7" />
<property name="java.source" value="1.7" />12

两个参数 
我的sdk中原来的是1.5,这样就导致了一些问题。在我的代码中使用了1.7的新特性,比如switch(string),在编译的时候javac的过程就会报错说1.5不支持这个特性,让使用高版本的jdk,所以我去把sdk中的改成了1.7,如果你使用了1.8的特性,那就改成1.8,同时环境变量的配置也要指向jdk1.8。

参考的文章有:1,2,3,4,表示感谢。

最后,附上一份我自己的项目的脚本代码

点击去下载


最新回复 (0)
返回