http://blog.csdn.net/t12x3456/article/details/7957117
由于公司运维需要以及应用中需要加上应用推广的统计,往往要对应二三十个渠道,按照正常方法一个一个的去生成不同渠道包的应用,不仅浪费了时间,而且大大降低了效率.
上一篇讲到使用Ant进行Zip/Tar包的解压缩,实际上Ant工具不仅仅具有此类功能,它更强大的地方在于自动化调用程序完成项目的编译,打包,测试等. 类似于C语言中的make脚本完成这些工作的批处理任务. 不同于MakeFile的是,Ant是纯Java编写的,因此具有很好的跨平台性.
在此我主要讲下如何自动构建工具Ant, 对应用进行批量打包, 生成对应不同市场的应用:
首先分别看一下用于打包的Java工程AntTest和需要被打包进行发布的Android工程结构:
market.txt里保存需要打包的市场标识,如:
youmeng
gfan
.......
此文件里自行根据需求添加渠道名称.
然后看一下实现批量打包AntTest类中的内容:
注意:红色标注部分需要进行修改:
- package com.cn.ant;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.File;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import org.apache.tools.ant.DefaultLogger;
- import org.apache.tools.ant.Project;
- import org.apache.tools.ant.ProjectHelper;
- public class AntTest {
- private Project project;
- public void init(String _buildFile, String _baseDir) throws Exception {
- project = new Project();
- project.init();
- DefaultLogger consoleLogger = new DefaultLogger();
- consoleLogger.setErrorPrintStream(System.err);
- consoleLogger.setOutputPrintStream(System.out);
- consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
- project.addBuildListener(consoleLogger);
- // Set the base directory. If none is given, "." is used.
- if (_baseDir == null)
- _baseDir = new String(".");
- project.setBasedir(_baseDir);
- if (_buildFile == null)
- _buildFile = new String(projectBasePath + File.separator
- + "build.xml");
- // ProjectHelper.getProjectHelper().parse(project, new
- // File(_buildFile));
- <span style="color:#FF0000;">// 关键代码</span>
- ProjectHelper.configureProject(project, new File(_buildFile));
- }
- public void runTarget(String _target) throws Exception {
- // Test if the project exists
- if (project == null)
- throw new Exception(
- "No target can be launched because the project has not been initialized. Please call the 'init' method first !");
- // If no target is specified, run the default one.
- if (_target == null)
- _target = project.getDefaultTarget();
- // Run the target
- project.executeTarget(_target);
- }
- <span style="color:#FF0000;">private final static String projectBasePath = "D:\\android\\workspace3\\XXX";//要打包的项目根目录
- private final static String copyApkPath = "D:\\android\\apktest";//保存打包apk的根目录
- private final static String signApk = "XXX-release.apk";//这里的文件名必须是准确的项目名!
- private final static String reNameApk = "XXX_";//重命名的项目名称前缀(地图项目不用改)
- private final static String placeHolder = "@market@";//需要修改manifest文件的地方(占位符)
- </span>
- public static void main(String args[]) {
- long startTime = 0L;
- long endTime = 0L;
- long totalTime = 0L;
- Calendar date = Calendar.getInstance();
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss");
- try {
- System.out.println("---------ant批量自动化打包开始----------");
- startTime = System.currentTimeMillis();
- date.setTimeInMillis(startTime);
- System.out.println("开始时间为:" + sdf.format(date.getTime()));
- BufferedReader br = new BufferedReader(new FileReader("market.txt"));
- String flag = null;
- while ((flag = br.readLine()) != null) {
- // 先修改manifest文件:读取临时文件中的@market@修改为市场标识,然后写入manifest.xml中
- String tempFilePath = projectBasePath + File.separator
- + "AndroidManifest.xml.temp";
- String filePath = projectBasePath + File.separator
- + "AndroidManifest.xml";
- write(filePath, read(tempFilePath, flag.trim()));
- // 执行打包命令
- AntTest mytest = new AntTest();
- mytest.init(projectBasePath + File.separator + "build.xml",
- projectBasePath);
- mytest.runTarget("clean");
- mytest.runTarget("release");
- // 打完包后执行重命名加拷贝操作
- File file = new File(projectBasePath + File.separator + "bin"
- + File.separator + signApk);// bin目录下签名的apk文件
- File renameFile = new File(copyApkPath + File.separator + reNameApk
- + flag + ".apk");
- boolean renametag = file.renameTo(renameFile);
- System.out.println("rename------>"+renametag);
- System.out.println("file ------>"+file.getAbsolutePath());
- System.out.println("rename------>"+renameFile.getAbsolutePath());
- }
- System.out.println("---------ant批量自动化打包结束----------");
- endTime = System.currentTimeMillis();
- date.setTimeInMillis(endTime);
- System.out.println("结束时间为:" + sdf.format(date.getTime()));
- totalTime = endTime - startTime;
- System.out.println("耗费时间为:" + getBeapartDate(totalTime));
- } catch (Exception e) {
- e.printStackTrace();
- System.out.println("---------ant批量自动化打包中发生异常----------");
- endTime = System.currentTimeMillis();
- date.setTimeInMillis(endTime);
- System.out.println("发生异常时间为:" + sdf.format(date.getTime()));
- totalTime = endTime - startTime;
- System.out.println("耗费时间为:" + getBeapartDate(totalTime));
- }
- }
- /**
- * 根据所秒数,计算相差的时间并以**时**分**秒返回
- *
- * @param d1
- * @param d2
- * @return
- */
- public static String getBeapartDate(long m) {
- m = m / 1000;
- String beapartdate = "";
- int nDay = (int) m / (24 * 60 * 60);
- int nHour = (int) (m - nDay * 24 * 60 * 60) / (60 * 60);
- int nMinute = (int) (m - nDay * 24 * 60 * 60 - nHour * 60 * 60) / 60;
- int nSecond = (int) m - nDay * 24 * 60 * 60 - nHour * 60 * 60 - nMinute
- * 60;
- beapartdate = nDay + "天" + nHour + "小时" + nMinute + "分" + nSecond + "秒";
- return beapartdate;
- }
- public static String read(String filePath, String replaceStr) {
- BufferedReader br = null;
- String line = null;
- StringBuffer buf = new StringBuffer();
- try {
- // 根据文件路径创建缓冲输入流
- br = new BufferedReader(new FileReader(filePath));
- // 循环读取文件的每一行, 对需要修改的行进行修改, 放入缓冲对象中
- while ((line = br.readLine()) != null) {
- // 此处根据实际需要修改某些行的内容
- if (line.contains(placeHolder)) {
- line = line.replace(placeHolder, replaceStr);
- buf.append(line);
- } else {
- buf.append(line);
- }
- buf.append(System.getProperty("line.separator"));
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- // 关闭流
- if (br != null) {
- try {
- br.close();
- } catch (IOException e) {
- br = null;
- }
- }
- }
- return buf.toString();
- }
- /**
- * 将内容回写到文件中
- *
- * @param filePath
- * @param content
- */
- public static void write(String filePath, String content) {
- BufferedWriter bw = null;
- try {
- // 根据文件路径创建缓冲输出流
- bw = new BufferedWriter(new FileWriter(filePath));
- // 将内容写入文件中
- bw.write(content);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- // 关闭流
- if (bw != null) {
- try {
- bw.close();
- } catch (IOException e) {
- bw = null;
- }
- }
- }
- }
- }
然后是Android工程中需要进行修改的部分:
1. 修改local.properties中的sdk根目录:
sdk.dir=D:\\android\\android-sdk-windows-r17\\android-sdk-windows-r17
2. 修改ant.properties中签名文件的路径和密码(如果需要)
key.store=D:\\android\\mykeystore
key.store.password=123456
key.alias=mykey
key.alias.password=123456
3. 修改AndroidManifest.xml.temp
拷贝AndroidManifest.xml一份,命名为AndroidManifest.xml.temp
将需要替换的地方改为占位符,需与打包工程AntTest中的placeHolder常量一致
如: <meta-data android:value="@market@" android:name="UMENG_CHANNEL"/>
4. Build.xml中:
<project name="XXX" default="help">,XXX必须为Android工程名称.
如果机器没有配置过Ant环境变量,可根据如下步骤进行配置:
ANT环境变量设置:
Windows下ANT用到的环境变量主要有2个,ANT_HOME 、PATH。
设置ANT_HOME指向ant的安装目录。
设置方法:
ANT_HOME = D:/apache_ant_1.7.0
将%ANT_HOME%/bin; %ANT_HOME%/lib添加到环境变量的path中。
设置方法:
PATH = %ANT_HOME%/bin; %ANT_HOME%/lib
Ant 是什么?
Apache Ant 是一个基于 Java 的生成工具。据最初的创始人 James Duncan Davidson 介绍,这个工具的名称是 another neat tool(另一个整洁的工具) 的首字母缩写。
Ant的作用:
生成工具在软件开发中用来将源代码和其他输入文件转换为可执行文件的形式(也有可能转换为可安装的产品映像形式)。随着应用程序的生成过程变得更加复杂,确保在每次生成期间都使用精确相同的生成步骤,同时实现尽可能多的自动化,以便及时产生一致的生成版本,这就变得更加重要.
Ant的优势:
Ant是一种基于Java的build工具。理论上来说,它有些类似于(Unix)C中的make ,但没有make的缺陷。目前的最新版本为:Ant 1.8.4。 既然我们已经有了make, gnumake, nmake, jam以及其他的build工具为什么还要要一种新的build工具呢?因为Ant的原作者在多种(硬件)平台上开发软件时,无法忍受这些工具的限制和不便。类似于make的工具本质上是基于shell(语言)的:他们计算依赖关系,然后执行命令(这些命令与你在命令行敲的命令没太大区别)。这就意味着你可以很容易地通过使用OS特有的或编写新的(命令)程序扩展该工具;然而,这也意味着你将自己限制在了特定的OS,或特定的OS类型上,如Unix.
Ant就不同了。与基于shell命令的扩展模式不同,Ant用Java的类来扩展。(用户)不必编写shell命令,配置文件是基于XML的,通过调用target树,就可执行各种task。每个task由实现了一个特定Task接口的对象来运行。
Ant 定义生成文件之间的依赖关系,它使用跨平台的 Java 类。使用 Ant,您能够编写单个生成文件,这个生成文件在任何 Java 平台上都一致地操作(因为 Ant 本身也是使用 Java 语言来实现的),这就是 Ant 最大的优势
Ant生成文件剖析:
Ant 没有定义它自己的自定义语法;相反,它的生成文件是用 XML 编写的。存在一组 Ant 能够理解的预定义 XML 元素,而且还可以定义新的元素来扩展 Ant 的功能。每个生成文件由单个 project 元素组成,该元素又包含一个或多个 target 元素。一个目标(target)是生成过程中已定义的一个步骤,它执行任意数量的操作,比如编译一组源文件。并且这些操作本身是由其他专用任务标签执行的然后这些任务将根据需要被分组到各个 target 元素中。一次生成过程所必需的所有操作可以放入单个 target 元素中,但是那样会降低灵活性。将那些操作划分为逻辑生成步骤,每个步骤包含在它自己的 target 元素中,这样通常更为可取。这样可以执行整体生成过程的单独部分,却不一定要执行其他部分。
例如,通过仅调用某些目标,您可以编译项目的源代码,却不必创建可安装的项目文件
顶级 project 元素需要包含一个 default 属性,如果在 Ant 被调用时没有指定目标,这个属性将指定要执行的目标。然后需要使用 target 元素来定义该目标本身。
下面是一个最基本的生成文件:
- <?xml version="1.0"?>
- <project default="init">
- <target name="init">
- </target>
- </project>
Ant基本使用方式 :
1. 配置环境变量:
ANT_HOME: C:\ant-1.8 -----> Ant的安装/解压目录路径
PATH后追加: C:\ant-1.8\bin ------>Ant中的BIN目录路径
2. 确认环境变量配置是否成功
打开CMD窗口,然后输入命令: ant:
看到如下显示:
由于Ant构建时需要默认有build.xml文件,因此有如上的提示,至此,说明Ant的环境已经配置成功.
3. 使用Ant创建一个名为HelloWorld的文件夹:
首先需要编辑build.xml:
- <?xml version="1.0"?>
- <project default="init">
- <target name="init">
- <span style="color:#FF0000;"><mkdir dir="HelloWorld"></span>
- </target>
- </project>
然后切换到build.xml文件所在的目录,输入ant,若有如下提示,则创建文件夹成功:
(init 部分相当于 日志的输出)
4. 也可以使用ant创建多级嵌套的文件目录
只需要在build.xml文件中进行修改:
- <?xml version="1.0"?>
- <project default="init">
- <target name="init">
- <span style="color:#FF0000;"> <mkdir dir="HelloWorld\a\b\c"/></span>
- </target>
- </project>
5. 删除如上的多级目录:
- <?xml version="1.0"?>
- <project default="init">
- <target name="init">
- <span style="color:#FF0000;"><delete dir="HelloWorld"/></span>
- </target>
- </project>
注意:此处路径只用输入最高级目录路径,这也正是ANT工具的强大之处:
Java中如果要删除目录,除非该目录为空才可以删除,否则就要逐步进行删除.
而使用Ant工具,则可以直接删除含有子目录的文件夹.
自动构建工具Ant深入剖析(二) Ant属性、目标、依赖深度详解
- 首先看一个示例XML文件:
- <?xml version="1.0"?>
- <project default="init" name="Project">
- <description>
- A simple project introducing the use of descriptive tags in Ant build files.
- </description>
- <!-- XML comments can also be used -->
- <target name="init" description="Initialize Argon database">
- <!-- perform initialization steps here -->
- </target>
- </project>
可以看出,XML 注释可以使用在整个生成文件中以提高清晰性。而且,Ant 定义了它自己的description 元素和 description 属性,它们可用于提供更结构化的注释。
Ant属性:
Ant 中的属性类似编程语言中的变量,它们都具有名称和值。然而与通常的变量不同,一经设置,Ant 中的属性就不可更改;它们是不可变的,就像 Java 语言中的 String 对象。 这起初看来似乎很有限制性,但这样是为了遵循 Ant 的简单原则: 毕竟,它是一个生成工具,而不是一种编程语言。如果尝试给某个现有属性赋予一个新的值,这不会被看作是一个错误,但是该属性仍然会保留其现有值
定义与使用属性:
<property name="metal“ value="beryllium"/>
为了在生成文件的其他部分引用这个属性,使用以下语法:
${metal}
例如,为了使用这样一个值,它是另一个属性的值的组成部分,将标签写成下面这样
- <property name="metal-database" value="${metal}.db"/>
location属性:
属性经常用于引用文件系统上的文件或目录,但是对于使用不同路径分隔符(例如,/ 与 \)的平台来说,这样可能在跨越不同平台时导致问题。Ant 的 location 属性专门设计用于以平台无关的方式包含文件系统路径。像下面这样使用 location 来代替value:
<property name="database-file" location="archive/databases/${metal}.db"/>
用于 location 属性的路径分隔字符将被转换为当前平台的正确格式;而且由于文件名是相对的,它被认为是相对于项目的基目录。我们同样可以容易地写为下面这样:
<property name="database-file" location="archive\databases\${metal}.db"/>
这个标签的两个版本都会在不同的平台具有相同的行为.
定义依赖关系:
生成一个项目一般需要许多步骤 —— 例如首先要编译源代码,然后将它打包为 Java 归档文件
(Java Archive File,JAR)。这其中许多步骤都具有清楚定义的顺序 —— 例如,在编译器从源代码生成类文件之前,您不能打包类文件。与顺序指定 target 所不同的是,Ant 采用一种更灵活的方法来定义 依赖关系 。每个目标的定义依据的是在它能够执行之前必须完成的其他所有目标。这是使用 target 元素的 depends 属性来实现的
<target name="init"/>
<target name="preprocess" depends="init"/>
<target name="compile" depends="init,preprocess"/>
<target name="package" depends="compile"/>
这种方法允许您执行项目任何阶段的生成过程;Ant 会首先执行已定义的先决阶段。在上面的例子中,如果让 Ant 完成 compile 步骤,它将判断出需要首先执行 init 和preprocess 这两个目标。init 目标不依赖其他任何目标,因此它将首先被执行。然后 Ant 检查 preprocesstarget,发现它依赖 init 目标;由于已经执行了后者,Ant 不会再次执行它,因而开始执行 preprocess 目标。最后可以执行 compile 任务本身。
注意目标出现在生成文件中的顺序并不重要:执行顺序是由depends 属性唯一确定的。
自动构建工具Ant深入剖析(三) Ant目标深度详解、使用Ant的各种任务
从命令行运行Ant:
从命令提示符调用 Ant 可以简单得只需键入单独的 ant。如果您这样做,Ant 将使用默认的生成文件;该生成文件中指定的默认目标就是 Ant 尝试要生成的目标。还可以指定许多命令行选项,后面跟着任意数量的生成目标,Ant 将按顺序生成这其中的每个目标,并在此过程中解决所有依赖关系.
命令行选项:
默认情况下,Ant 寻找一个名为build.xml 的文件。因此,如果您的生成文件使用这个名称,就不需要在命令行指定它。当然,有时使用具有其他名称的生成文件更方便,在那样的情况下,您需要对 Ant 使用 -buildfile <file> 参数(-f <file> 是其简写形式)
–-Dmetal=beryllium
这个功能可用于覆盖生成文件中的初始属性设置。正如前面指出过的,属性的值一经设置就不能改变。-D 标志在读取生成文件中的任何信息之前设置某个属性;由于生成文件中的指派落在这个初始指派之后,因此它不会改变其值
编译源代码:
–<javac srcdir="src"/>
这个标签寻找 src 目录中以 .java 为扩展名的所有文件,并对它们调用 javac 编译器,从而在相同的目录中生成类文件。当然,将类文件放在一个单独的目录结构中通常会更清晰;可以通过添加destdir 属性来让 Ant 做到这点.
其他有用的属性包括
–classpath:等价于 javac 的 -classpath 选项。
–debug="true":指示编译器应该带调试信息编译源文件。
javac 任务的一个重要特点在于,它仅编译那些它认为需要编译的源文件。如果某个类文件已经存在,并且对应的源文件自从该类文件生成以来还没有改变过,那么该源文件就不会被重新编译。
javac任务的输出显示了实际被编译的源文件的数目。
这种行为刻画了 Ant 的许多任务的特点:如果某个任务能够确定所请求的操作不需要执行,那么该操作就会被跳过。
创建 JAR 文件:
在编译 Java 源文件之后,结果类文件通常被打包到一个 JAR 文件中,这个文件类似 zip 归档文件。每个 JAR 文件都包含一个清单文件,它可以指定该 JAR 文件的属性
下面是 Ant 中 jar 任务的一个简单使用例子:
<jar destfile="package.jar" basedir="classes"/>
这将创建一个名为 package.jar 的 JAR 文件,并把 classes 目录中的所有文件添加到其中(JAR文件能够包含任意类型的文件,而不只是类文件)。此处没有指定清单文件,因此 Ant 将提供一个基本的清单文件
manifest 属性允许指定一个用作该 JAR 文件的清单的文件。清单文件的内容还可以使用 manifest任务在生成文件中指定。这个任务能够向文件系统写入一个清单文件,或者能够实际嵌套在 jar 之内,以便一次性地创建清单文件和 JAR 文件。
- <strong><jar destfile="package.jar" basedir="classes">
- <manifest>
- <attribute name="Built-By" value="${user.name}"/>
- <attribute name="Main-class" value="package.Main"/>
- </manifest>
- </jar></strong>
时间戳生成:
在生成环境中使用当前时间和日期,以某种方式标记某个生成任务的输出,以便记录它是何时生成的,这经常是可取的。这可能涉及编辑一个文件,以便插入一个字符串来指定日期和时间,或将这个信息合并到 JAR 或 zip 文件的文件名中。
这种需要是通过简单但是非常有用的 tstamp 任务来解决的。这个任务通常在某次生成过程开始时调用,比如在一个 init 目标中。这个任务不需要属性,许多情况下只需 <tstamp/> 就足够了
tstamp 不产生任何输出;相反,它根据当前系统时间和日期设置 Ant 属性。下面是 tstamp 设置的一些属性、对每个属性的说明,以及这些属性可被设置到的值的例子:
在调用 tstamp 任务之后,我们能够根据日期命名该 JAR 文件,如下所示:
<jar destfile="package-${DSTAMP}.jar" basedir="classes"/>
文件系统操作:
创建和删除目录
–最基本的文件系统操作之一就是创建目录或文件夹。做这项工作的任务名为 mkdir
–<mkdir dir="archive/metals/zinc"/>
–mkdir 任务的另一个有用特性是它的如下能力:在父目录还不存在时创建它们
–如果目标目录已经存在,mkdir 任务不会发出错误消息,而只是假设它的工作已经完成,从而什么也不做
删除目录
–<delete dir="archive/metals/zinc"/>
–这将删除指定的目录连同它包含的所有文件以及子目录。使用 file 属性而不是 dir 属性可以指定要删除的单个文件
复制和移动文件及目录
<copy file="src/Test.java" tofile="src/TestCopy.java"/>
还可以使用 move 来执行重命名操作而不是拷贝文件
<move file="src/Test.java" tofile="src/TestCopy.java"/>
另一个常用的文件系统操作是将文件复制或移动到另一个目录
<copy file="src/Test.java" todir="archive"/>
<move file="src/Test.java" todir="archive"/>
默认情况下,Ant 仅输出它执行的移动和复制操作的摘要,包括诸如已移动或复制的文件的数量等信息。如果想看到更详细的信息,包括涉及的文件名称等,可以将 verbose 属性设置为true
创建和解压缩zip文件:
<zip destfile="output.zip" basedir="output"/>
解压缩和提取文件:
<unzip src="output.tar.gz" dest="extractDir"/>
还可以包括 overwrite 属性来控制覆盖行为。默认设置是覆盖与正在被提取的归档文件中的条目相匹配的所有现有文件
替换文件中的标记:
replace 任务,它执行文件中的查找和替换操作。
token 属性指定要查找的字符串,value 属性指定一个新的字符串,查找到的标记字符串的所有实例都被替换为这个新的字符串。例如:
<replace file="input.txt" token="old" value="new"/>
替换操作将在文件本身之内的适当位置进行。为了提供更详细的输出,可把 summary 属性设置为true。这将导致该任务输出找到和替换的标记字符串实例的数目
模式匹配:
可以对目录执行模式匹配。例如,模式 src*/*.java 将匹配带 src 前缀的任何目录中的所有Java 文件。
还有另一种模式结构:**,它匹配任意数量的目录。例如,模式 **/*.java 将匹配当前目录结构下的所有 Java 文件。
<copy todir="archive">
–<fileset dir="src">
<include name="*.java"/>
–</fileset>
</copy>
fileset 默认情况下包含指定 src 目录下的所有文件,因此为了仅选择 Java 文件,我们对模式使用一个 include 元素。类似地,我们可以对另一个模式添加一个 exclude 元素,从而潜在地排除include 指定的匹配项。甚至可以指定多个include 和 exclude 元素;这样将得到一组文件和目录,它们包含 include 模式的所有匹配项的并集,但排除了 exclude 模式的所有匹配项
默认排除:
自动从文件集内容中排除的内置模式列表。该列表包括与名为 CVS的目录相匹配的条目,以及以 ~ 字符结尾的文件,它们可能是备份文件。通常不想在文件系统操作中包括这类文件和目录,因此排除这些文件是默认行为。然而,如果确实想无例外地选择 所有 文件和目录,可以将文件集的 defaultexcludes 属性设置为 no。
使用CVS:
CVS任务的主要属性是 cvsRoot,它是对 CVS 库的完整引用,包括连接方法和用户详细信息。
这个参数的格式如下
[:method:][[user][:password]@]hostname[:[port]]/path/to/repository
提取是 CVS 任务的默认操作;其他操作可通过使用command 属性来指定
使用自定义任务进行扩展ANT:
我们将考察一个简单自定义任务的构造过程。这个任务将对文件中的行执行排序操作,并将排序后的行集写到一个新文件中
创建自定义的任务:
为实现一个简单的自定义任务,我们所需做的就是扩展 org.apache.tools.ant.Task 类,并重写execute() 方法。
大多数任务,不管是核心任务还是自定义任务,都利用属性来控制它们的行为。对于这个简单任务,我们需要一个属性来指定要排序的文件,需要另一个属性来指定排序内容的输出。我们把这两个属性分别叫做 file 和 tofile
在能够调用自定义的任务之前,我们需要给它指定一个名称来 定义 它,并告诉 Ant 关于实现这个任务的类文件的信息,以及定位该类文件所必需的任何 classpath 设置。这是使用 taskdef 任务来完成的,如下所示:
- <taskdef name="filesorter"
- classname="FileSorter"
- classpath="."/>