2014年7月13日,下文中的ADT版本为adt-bundle-windows-x86_64-20140624。
上半年项目需要,开发一个基于Android SDK的应用,通过仔细学习Android SDK提供的build.xml文件,发现对Ant的认识上升到了一个新的高度,今天把项目中遇到的知识点记录下来。
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.properties、proguard-project.txt、local.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相关的的注释起就产生了很大的兴趣。从注释看,proguard是SDK自带的工具,但做什么的看不出来,不过貌似很重要。从网上的资料得知,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执行时会自动载入这个文件里的配置。这里学到一招,通过Ant的property标签加载属性文件,当文件不存在时不会报错。
5) custom_rules.xml
通过阅读build.xml可以发现,如果项目路径下存在名为custom_rules.xml的文件,Ant会自动加载这个文件里定义的Target,程序员可以把扩展点的Target定义到这个文件里,避免build.xml内容过多。这里学到一招,通过Ant的import标签加载任务文件,可以指定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脚本里定义了两类扩展点,一类是属性值,另外一类是预定义的Target,SDK定义的build.xml文件里预定义好了相关的依赖,项目构建时会自动引用。
程序员自定义的属性值可以写到ant.properties里,而自定义的Target可以定义到custom_rules.xml里。
比较惭愧,只用过少数几个属性,多数都没有用到。
默认值:空字符串
用途:用于指定adb访问的设备名称,除非设备名称是固定不变的,否则最好的方法还是在执行build.xml时,通过命令行参数的形式传递给Ant,样例如下:
ant -Dadb.device.arg=-d 真实设备
ant -Dadb.device.arg=-e 模拟器
默认值:空字符串
用途:针对src目录设定的过滤规则,避免某些文件被打包进最终的APK中。
默认值:空字符串
没有尝试过,不知道是不是可以覆盖AndroidManifest.xml文件中的定义。
默认值:空字符串
没有尝试过,不知道是不是可以覆盖AndroidManifest.xml文件中的定义。
默认值:空字符串
默认值:空字符串
用途:针对res和assets目录设定的过滤规则,避免目录内的某些文件被打包进APK。过滤规则的定义:[!][<dir>|<file>][*suffix-match|prefix-match*|full-match],样例如!.svn:!.git,这个特性还是比较有用的。
默认值:false
用途:没用过
默认值:false
用途:没有用过
默认值:UTF-8
用途:Java代码编译时,假如代码源文件的编码方式和编译器指定的编码方式不同,有可能会导致编译失败。
默认值:1.5
用途:指定编译源码时生成class文件的版本。我在项目开发时只用过1.6,但使用最新的SDK编译,发现1.7也是可以支持的。
默认值:1.5
用途:源代码的版本。同上,现在使用新一点的SDK,这里可以配置为1.7。
默认值:空字符串
用途:设置编译参数。比如启用lint
<compilerarg value="-Xlint"/>
默认值:空字符串
用途:设置编译参数,没用过。
默认值:O0
默认值:O3
默认值:false
默认值:false
默认值:空字符串
默认值:false
默认值:bin/lint-results.html
默认值:bin/lint-results.xml
分析SDK提供的模板build.xml文件,可以找到如下的几个可供扩展用的Target,-pre-clean、-pre-build、-pre-compile、-post-compile、-post-package、-post-build。单个命名就可以理解它们的用途,因而不需要做特别的解释。
我对release、debug、clean、instrument这几个Target比较感兴趣,所以对它们做了深入的学习。手头上没有像样的工具,截图是用XMind画的,看起来效果还不错。绿色的笑脸表示可由程序员自定义扩展的Target。但是从Word里向网页里贴图失败,所以只好上传附件。
安卓SDK里提供的build.xml只有大约1500行,但从中可以看出Google为了推广Android项目的确是不遗余力,在ADT的方方面面都投入了相当的精力,以尽可能的降低安卓程序员入门的门槛,提高安卓应用的开发效率。有这样的模板build.xml文件,程序员开发安卓应用时,基本不需要在打包脚本上投入太多的精力就可以完成相关工作。
在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 |
根据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好处很多,这里不一一介绍,只讲解在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