最近在项目中遇到了一些打包的问题,顺便去了解了下打包的一些知识点。这里主要介绍和总结了一下ant、build.xml的知识点以及构建apk和jar包的一些注意事项。
Android打包
对工程代码和资源文件使用打包工具进行编译、混淆、签名、优化对齐等一系列步骤之后生成可发布到应用市场的apk的构建过程。
打包流程
build.png
大概分为以下几个步骤
1、使用aapt工具将res资源文件生成R.java文件
2、使用aidl工具将aidl文件生成对应java文件
3、使用javac命令编译工程源代码和上面两步生成的文件,生成class文件
4、通过dex工具将class文件和第三方jar包打成dex文件
5、用aapt工具将res下的资源文件编译成二进制文件,然后将其和上一步中的dex文件以及assets中的文件通过apkbuilder工具打包成apk文件
6、通过jarsigner对apk进行签名
7、利用zipalign工具对apk进行字节对齐优化操作
打包方式 — Ant
Ant是将软件编译、测试、部署等步骤联系在一起自动化构建工具,主要用在java工程的构建中,所以也可以用来进行android打包。
现在android开发工具基本上都用的AS,构建用gradle,由于某些原因,项目组中用的还是eclipse+ant方式,所以暂时只介绍ant的构建方式。虽然工具不一样,但是整个构建原理和流程还是一样的。
Ant的默认构建文件为build.xml,输入ant命令后,ant会在当前目录下搜索是否有build.xml,如果有,则执行该文件,也可以自定义构建文件,通过ant -f test.xml即可指定test.xml为构建文件。
build.xml脚本
先看一个简单的build.xml
//ant默认构建文件即build.xml文件中需定义一个唯一的项目(Project标签),Project下可以定义若干个目标(target标签)
//project名称为MyApp, default表示默认的运行target,为必须属性,如果ant命令没有指定target时,则运行default属性中的target
//如MyApp工程目录下直接输入ant命令,则会直接打debug包。 basedir表示项目的基准目录
//property标签用来设置属性值,可以通过file标签来指定要加载的属性文件的路径,加载后属性文件中的指定的属性可以直接引用。
//为了方便配置,可以将环境变量声明在build.properties中,并通过file引入到build.xml中
//property中的name表示属性的名称 value表示属性值 在其他地方可以通过${属性名}进行引用, 类似于定义一个变量
//tartget,表示一个构建目标,也可以看成一个构建步骤, 一次构建过程中会执行一个或者多个构建步骤。
//target中的depends属性表示target之间的依赖关系,一个target可以依赖其他的target标签,depends属性也指定了target的执行顺序。
//ant会按照depends属性中target的顺序来依次执行每个target。所以本文中target的执行顺序为 targetone -> targettwo -> debug
//创建目录
//task是target中的子元素,一个target中可以有多个task,类似于target的子任务,常用的task有echo、mkdir、delete、javac、java等等
//删除目录
//输出日志信息
debug target perform...
打包成apk
build脚本中,一般android源工程打包成apk的执行步骤大体如下:
gen-R->aidl->compile->obfuscate->dex->package-res-and-assets->package->jarsigner->zipalign->release
gen-R之前还有一些clean清除上次打包产生的文件的操作,这里不再赘述
1、gen-R
gen-R 执行aapt命令来编译资源文件生成R.java文件 arg中的参数就是aapt中的命令行参数,该target其实执行的就是如下命令
aapt package -m -J gen -M AndroidManifest.xml -S res -I android.jar
具体参数命令含义如下:
-m 使生成的包的目录放在-J参数指定的目录。
-J 指定生成的R.java的输出目录
-M AndroidManifest.xml的路径
-S res文件夹路径
-I 某个版本平台的android.jar的路径
2、aidl
此步骤主要是生成aidl文件对应的java文件
使用apply标签可以进行批量运行task,此步骤即用build-tools下的aidl工具对src文件夹下的所有aidl文件进行批量转换成java文件。
是作为的一个子类而被实现,所以任务的所有属性,都可以用于
3、compile
//javac标签用于编译java文件生成class文件 destdir表示生成class文件的目录
bootclasspath="${android-jar}" fork="true" memoryMaximumSize="512m" >
//表示依赖库的路径 内嵌在、中
compile执行的是javac命令,编码格式指定为utf-8,target指定生成的class文件与该版本的虚拟机兼容,保证在该版本的虚拟机上正常运行。
debug表示是否产生调试信息,默认为false。extdirs为扩展文件的路径,destdir指定了存放编译后的class文件的文件夹路径。bootclasspath指定了编译过程中需要导入的class文件。fork指定是否再外部启用一个新的JDK编译器来执行编译,如果为false,则javac命令和ant将在同一个进程中执行,并且javac命令被分配的内存只有64MB,可能会导致java.lang.OutOfMemoryError(OOM)错误,如果fork为true,则另起一个进程来执行javac命令,分配的内存大小将由memoryMaximumSize来指定。src指定了java源文件的路径,classpath指定了依赖的第三方jar包路径。
4、obfuscate
//jar标签用来生成jar文件,basedir表示需要打包城jar文件的原文件目录, destfile表示生成的jar文件
//java标签用来执行编译生成的class文件 fork表示再一个新的虚拟机中运行该类 failonerror表示当出现错误时是否自动停止
//arg标签用来指定参数 value是命令行参数
obfuscate混淆先是执行了jar命令,将bin目录下的class文件打包成temp.jar。然后执行了proguard命令来压缩、优化和混淆操作。
-injars {class_path}指定要处理的应用程序jar和目录,即temp.jar
-outjars {class_path}指定处理完后要输出的jar和目录,即obfuscate.jar
-libraryjars {classpath}指定要处理的应用程序jar和目录所需要的程序库文件,即其他依赖的第三方jar包
混淆配置文件为proguard.config。混淆之后删除生成的临时文件,并解压obfuscate.jar到bin目录下
5、dex
dex就是用dx.bat工具将class文件转换成classes.dex文件,即对上一步在bin/classes目录中生成的优化过的class文件以及依赖的第三方jar包进行dex操作,最后在bin目录下生成classes.dex文件。Parallel用于指定将多个task并行执行。
6、package-res-and-assets
package-res-and-assets中执行了aapt命令,来将res、assets目录下的资源文件打包到resources.ap_
aapt package -f -M -S -A -I -F
7、package
通过apkbuilder.bat工具根据classes.dex文件和resources.ap_生成未混淆的apk包
apkbuilder -z -f -rf -rj -nf
8、jarsigner
jarsigner是对上面生成的apk文件进行签名操作
-verbose 签名时输出详细信息
-storepass 密钥库密码
-keystore 密钥库位置
-signedjar 后面接的参数依次是 签名后的apk、待签名的apk、密钥库别名
9、zipalign
zipalign target通过zipalign工具对签名后的apk包进行字节对齐,好处是能够减少应用程序的RAM内存资源消耗
-v 表示输出详细信息
-f 表示如果输出文件已存在 则直接覆盖
4 表示对齐为4个字节
10、release
......
至此打一个完整的带签名的可发布的包的流程就结束了。执行ant release命令即可完成打包。
打包成jar
由于jar包中不能包含资源文件,所以要通过jar包提供UI视图供第三方使用,可以通过如下方式实现:
1、使用硬编码来实现布局文件
2、布局中的资源文件需放在assets文件夹中,然后打包到jar中,通过流的方式读取。这种方式将资源文件放在assets目录下和java代码一起打包为jar,其他工程依赖该jar包时,可以只引用jar包,不需要再额外导入资源文件,在该工程编译应用时会将jar包assets目录中的文件与该工程中的assets目录中的文件合并。注意assets目录中的文件名与所导入工程中的文件名称不能重复,否则在编译的时候会报错“Error generating final archive: Found duplicate file for APK”提示有重名文件。
另外,打包到jar中的资源文件必须是编译之后的资源文件,即编译成二进制文件,因为读取资源时是通过流的方式读取的,所以相关的资源文件必须在编译成二进制文件之后再放入assets打包。
读取方式如下
//读取图片
InputStream inputStream = context.getAssets().open(path);
Drawable drawable = Drawable.createFromResourceStream(
context.getResources(), value, inputStream, name);
//读取xml图片资源
XmlResourceParser parser = context.getAssets().openXmlResourceParser(path);
Drawable draw = Drawable.createFromXml(context.getResources(), parser);
jar包的构建方式与apk的类似,执行步骤大概为
aidl->compile->copy_asset->obfuscate->jarsigner
与打包成apk流程相比少了gen-R、aapt、dex、package-res-and-assets、package、zipalign等操作,需要注意就是obfuscate混淆这一步,
打成jar包时obfuscate如下:
Obscure the class files....
...
obfuscate混淆先是执行了jar命令,将bin目录下的class文件以及资源文件打包成jar包,然后执行proguard命令来压缩、优化和混淆操作。这里需要注意的是如果该工程还依赖了其他jar包(未混淆),则打成jar的同时需要将其他jar包也引入进来,因为最后对外提供的是该工程的jar包。
另外需要注意的是proguard.cfg混淆文件中需要为其他jar包的类文件指明重命名类的包路径
# Specifies to repackage all class files that are renamed, by moving them into the single given package
-repackageclasses 'com.example.otherjar'
一定要为一些重命名的class文件指明打到jar包中的包路径,jar包中所有的class文件需要有明确的包路径,以防被第三方apk集成编译时,这些class文件无法-keep,被编译混淆之后找不到这些类,导致jar包功能异常。
rename1.png
而增加了路径指定后,重命名的类就会被打到指定的包路径下,其他地方对这些类的调用也能正常进行。
rename2.png
基本上要介绍的就这么多,可能会有理解错误的地方,欢迎一起讨论!