使用Ant构建Android应用

2014713日,下文中的ADT版本为adt-bundle-windows-x86_64-20140624

 

上半年项目需要,开发一个基于Android SDK的应用,通过仔细学习Android SDK提供的build.xml文件,发现对Ant的认识上升到了一个新的高度,今天把项目中遇到的知识点记录下来。

创建build.xml

Android SDK提供了从无到有创建安卓应用工程的命令,但我没有尝试过,所以没有使用经验。Android SDK里的工具功能很多、很强大,并且有详尽的帮助信息,下面对命令的使用方法尽可以查阅帮助,命令样例如下。

%android_home%\Tools\android -h 

%android_home%\Tools\android -h create project

%android_home%\Tools\android -h update project

创建build.xml文件的步骤:

1) 在ADT里,创建应用的项目,项目所在目录名为比如%ANDROID_PROJ%

2) 调用SDK里的命令,查看当前SDK支持的平台,%android_home%\Tools\android list target

3) 调用SDK里的命令,创建build.xml文件,命令为%android_home%\Tools\android update project -p %ANDROID_PROJ% -s -t 1

 

准备知识

根据这条命令的输出,可以知道工具生成了build.xml,同时更新了project.propertiesproguard-project.txtlocal.properties。由于Android项目发展比较快,所以前述文件的格式可能会随着SDK的版本变化而变化,而这几个文件非常容易生成,这里就不再引用各文件的内容了。

继续之前,先简单介绍一下Android应用项目里的文件。

1) project.properties

从文件的注释可以看出,这个文件是受SDK工具管理的,一般不需要程序员手工修改,看到这里,相信如果不是为了给自己惹麻烦的话,建议还是不要自做主张去修改这个文件吧。不过有例外,文件的注释里有提到,假如发布版本时需要启用proguard工具(proguard做什么的?),则要手工去掉某行内容的注释。

从现有的文件内容看,project.properties定义了应用支持的平台版本,以及依赖库的目录。

project.properties对于项目而言很重要,所以Android推荐程序员把这个文件合入到配置管理。从内容看,project.properties文件的内容和程序员本地的环境无关,所在同一个项目的成员应当可以使用同一份配置,不需要单独做定制。

2) proguard-project.txt

progurad是什么?从看到project.properties文件里proguard相关的的注释起就产生了很大的兴趣。从注释看,proguardSDK自带的工具,但做什么的看不出来,不过貌似很重要。从网上的资料得知,proguard工具的主要用途是代码混淆,提高逆向工程的代价,有效保护应用开发商的核心技术。proguard工具自带的资料比较详细,看起来似乎并不困难。

proguard-project.txt文件很重要,需要合入到配置管理。

3) local.properties

这个文件是由SDK工具生成的。目前仅有的用途是配置Android SDK的安装路径,这和程序员本地环境配置强相关,因而不建议合入配置管理。此外这个文件并不是必需的,通过阅读SDK生成的build.xml文件可以发现,定义系统变量ANDROID_HOME并指向SDK的安装路径,也可以达到把SDK的安装路径传递给Ant的目的。

4) ant.properties

通过阅读build.xml可以发现,如果项目路径下存在有名为ant.properties的文件,Ant执行时会自动载入这个文件里的配置。这里学到一招,通过Antproperty标签加载属性文件,当文件不存在时不会报错。

5) custom_rules.xml

通过阅读build.xml可以发现,如果项目路径下存在名为custom_rules.xml的文件,Ant会自动加载这个文件里定义的Target,程序员可以把扩展点的Target定义到这个文件里,避免build.xml内容过多。这里学到一招,通过Antimport标签加载任务文件,可以指定optional属性为true,这样当文件不存在时,也不会报错。

6) build.xml

构建过程中最重要的文件,出乎意料的是内容非常短,只有短短的不到100行,多数内容还都是注释,所以阅读起来不费力气。主要做了几件事情,加载local.properties文件,判断ANDROID_HOME变量是否存在,加载project.properties,加载custom_rules.xml,加载SDK目录下的build.xml

 

扩展点

这里要提到Ant的一个比较有意思的特点,build.xml脚本中属性值基于前置定义优化的原则,即属性发生重复定义时,前面定义的值不会被后面定义的覆盖,同理Target也遵守相同的原则,前面定义的Target不会被后面定义的Target覆盖,这就为模板脚本的开发工作带来了不少的便利。

根据这个特点,Android应用的build.xml脚本里定义了两类扩展点,一类是属性值,另外一类是预定义的TargetSDK定义的build.xml文件里预定义好了相关的依赖,项目构建时会自动引用。

程序员自定义的属性值可以写到ant.properties里,而自定义的Target可以定义到custom_rules.xml里。

 

预定义的属性

比较惭愧,只用过少数几个属性,多数都没有用到。

adb.device.arg

默认值:空字符串

用途:用于指定adb访问的设备名称,除非设备名称是固定不变的,否则最好的方法还是在执行build.xml时,通过命令行参数的形式传递给Ant,样例如下:

ant -Dadb.device.arg=-d 真实设备

ant -Dadb.device.arg=-e 模拟器

android.package.excludes

默认值:空字符串

用途:针对src目录设定的过滤规则,避免某些文件被打包进最终的APK中。

 

version.code

默认值:空字符串

没有尝试过,不知道是不是可以覆盖AndroidManifest.xml文件中的定义。

version.name

默认值:空字符串

没有尝试过,不知道是不是可以覆盖AndroidManifest.xml文件中的定义。

aapt.resource.filter

默认值:空字符串

 

aapt.ignore.assets

默认值:空字符串

用途:针对resassets目录设定的过滤规则,避免目录内的某些文件被打包进APK。过滤规则的定义:[!][<dir>|<file>][*suffix-match|prefix-match*|full-match],样例如!.svn:!.git,这个特性还是比较有用的。

dex.force.jumbo

默认值:false

用途:没用过

dex.disable.merger

默认值:false

用途:没有用过

java.encoding

默认值:UTF-8

用途:Java代码编译时,假如代码源文件的编码方式和编译器指定的编码方式不同,有可能会导致编译失败。

java.target

默认值:1.5

用途:指定编译源码时生成class文件的版本。我在项目开发时只用过1.6,但使用最新的SDK编译,发现1.7也是可以支持的。

java.source

默认值:1.5

用途:源代码的版本。同上,现在使用新一点的SDK,这里可以配置为1.7

java.compilerargs

默认值:空字符串

用途:设置编译参数。比如启用lint

<compilerarg value="-Xlint"/>

java.compiler.classpath

默认值:空字符串

用途:设置编译参数,没用过。

renderscript.debug.opt.level

默认值:O0

renderscript.release.opt.level

默认值:O3

 

renderscript.support.mode

默认值:false

manifestmerger.enabled

默认值:false

emma.filter

默认值:空字符串

 

verbose

默认值:false

lint.out.html

默认值:bin/lint-results.html

lint.out.xml

默认值:bin/lint-results.xml

 

 

预定义Target

分析SDK提供的模板build.xml文件,可以找到如下的几个可供扩展用的Target-pre-clean-pre-build-pre-compile-post-compile-post-package-post-build。单个命名就可以理解它们的用途,因而不需要做特别的解释。

 

编译目标

我对releasedebugcleaninstrument这几个Target比较感兴趣,所以对它们做了深入的学习。手头上没有像样的工具,截图是用XMind画的,看起来效果还不错。绿色的笑脸表示可由程序员自定义扩展的Target。但是从Word里向网页里贴图失败,所以只好上传附件。

 

后记

安卓SDK里提供的build.xml只有大约1500行,但从中可以看出Google为了推广Android项目的确是不遗余力,在ADT的方方面面都投入了相当的精力,以尽可能的降低安卓程序员入门的门槛,提高安卓应用的开发效率。有这样的模板build.xml文件,程序员开发安卓应用时,基本不需要在打包脚本上投入太多的精力就可以完成相关工作。

 

调试Ant脚本

eclipse里调试Ant脚本很方便,方法和调试Java代码一样。如果直接在命令行里检查Ant脚本的正确性,就比较麻烦、费时了。好在Ant在运行时提供了日志,通过增加-d选项,可以让Ant输出比较平常更多的信息,对于大型项目来说,这些信息对理解脚本在运行时的行为会非常有帮助。

 

输出路径变量的值

走读Android提供的模板脚本时,经常会看到变量project.all.jars.path。这个变量用于表示当前项目加载的全部Jar文件列表,但在安卓的模板脚本里没有提供定义,而且使用通常的echo 标签不能正常的把取值输出到标准输出。怎样才知道这个变量在脚本运行期的取值呢?有个简单的方法,使用Ant1.9版本,可以通过如下方式把路径变量的值输出到标准输出。

<echo>${toString:project.all.jars.path}</echo>

<echo>${project.all.jars.path}</echo>   <!-- 这行脚本无效 -->

在我的测试工程里输出如下,可以看到通常的echo标签没有生效。

-post-compile:

     [echo] E:\Android\adt\workspace\JackieFrog\libs\android-support-v4.jar

     [echo] ${project.all.jars.path}

 

运行时修改路径变量取值

比如在脚本运行时,希望动态修改路径变量project.all.jars.path的值,便于控制参与编译过程的Jar文件。这时可以利用安卓编译模板提供的预定义扩展点,在某个Target执行前把project.all.jars.path变量的值做调整。如下是样例

<target name="-post-compile">

<echo>${toString:project.all.jars.path}</echo>

    <path id="project.all.jars.path">

        <pathelement path="${sdk.dir}/tools/lib/ant-tasks.jar" />

    </path>

<echo>${toString:project.all.jars.path}</echo>

</target>

如下是样例输出

-post-compile:

     [echo] E:\Android\adt\workspace\JackieFrog\libs\android-support-v4.jar

     [echo] E:\Android\adt\sdk\tools\lib\ant-tasks.jar

 

APK签名

根据Android平台的安全要求,任何APK必须被签名,然后才允许被安装到终端上。当使用eclipse调试时,eclipse使用默认的私钥为APK签名,当然程序员通过配置自己的私钥来替换掉默认的私钥。

ant.properties文件中增加如下变量的定义:

key.store=app.keystore             #私钥文件
key.alias=www.example.com        #私钥的别名

key.store.password=app.password    #私钥的存储口令
key.alias.password=app.password    #私钥别名的口令

 

私钥可以预先生成,后续每次打包都使用相同的,也可以在每次打包时都生成新的。

custom_rules.xml文件中扩展-post-package

集成lombok

使用Lombok好处很多,这里不一一介绍,只讲解在Android应用项目中集成Lombok的方法。

步骤比较简单,如下操作:

1、下载lombok,路径http://projectlombok.org/downloads/lombok.jar

2、为ADT里的eclipse安装lombok,方法非常简单,具体可以问度娘;

3、生成接口Jar,命令java -jar lombok.jar publicApi,得到lombok-api.jar

4、把lombok-api.jar复制到安卓应用项目下的libs目录;

5、在安卓应用项目里创建一个名为compile-libs的目录,把lombok.jar放到compile-libs目录下;

6、在-post-compile中调整变量project.all.jars.path,避免把lombok-api.jar或者lombok.jar也打包进应用里。

条件编译

这个话题和Ant关系不大,只是附带提一下。Java语言没有提供类似C/C++的预处理能力,但有时真觉得的不方便,比如对于一些调试用的日志,出于性能或者其它方面的考虑,仅希望在测试时存在,版本发布之后就不希望它们再出现,这时就很麻烦,想点其它办法来解决。

 

如下是SDK生成的代码样例。

 

/** Automatically generated file. DO NOT MODIFY */

package jackie.garden.tools;

 

public final class BuildConfig {

    public final static boolean DEBUG = true;

}

 

上述代码有什么意义呢?先看下面一段代码。

 

public class Main {

 

private static final boolean debug = false;

/**

 * @param args

 */

public static void main(final String[] args) {

 

if (debug) {

System.out.println("hello, debug is true");

} else {

System.out.println("hello, debug is false");

}

}

}

 

代码比较简单,运行输出结果也是确定的,但奇妙的地方不在结果,而在于编译后生成的字节码。使用JD-GUI查看上述代码编译后得到的class文件,可以得到反编译后的代码如下所示。

import java.io.PrintStream;

 

public class Main

{

  private static final boolean debug = false;

  

  public static void main(String[] args)

  {

    System.out.println("hello, debug is false");

  }

}

代码源文件中的if判断消失了,难道javac在生成字节码时,直接忽略了源文件中的死代码?为了证实这一假设,可以尝试修改源文件中debug的值,重新用JD-GUI查看生成的字节码,操作过程比较简单,也非常有意思,这里就不再附上源码。

 

 

参考文档

Android开发者官网

http://www.cnitblog.com/zouzheng/archive/2011/01/12/72638.html

 

使用Ant构建Android应用,,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。