Android Dex分包
首先总结一下apk打包的一个大致流程,方便后边大家理解。
大致分为三步,每一步中又包含了一些详细的操作步骤:
我们如果想要生成.dex文件,必须通过工具将一些资源与java代码生成.class文件,其中包含一些资源文件、所有java代码文件、aidl文件等。首先需要在环境变量中配置好我们将要用到的工具,包括JDK、SDK、ANT,配置方法在这里就不多说了,网上一查一大片。然后我们先要用到SDK包下的一个aapt工具,这个工具后面还会被用到一次,工具在/SDK/build-tools/目录下,我们先用它生成应用的R.java文件。先cd到当前目录下然后输入aapt工具相关命令行及相关参数,如下:
aapt package -f -m -J C:\Users\jason\Desktop\ -S D:\lylsoft\android\androidstudio\opensource\MyButterKnifeDemo\app\src\main\res -M D:\lylsoft\android\androidstudio\opensource\MyButterKnifeDemo\app\src\main\AndroidManifest.xml -I D:\lylsoft\android\androidstudio\sdk\platforms\android-24\android.jar
参数含义如下:
-f 如果编译生成的文件已经存在,强制覆盖。
-m 使生成的包的目录存放在-J参数指定的目录
-J 指定生成的R.java 的输出目录路径(存放在桌面的gen)
-S 指定目标项目res文件夹的路径
-M 指定目标项目AndroidManifest.xml文件的路径
-I 指定某个版本平台的android.jar文件的路径
回车执行,便会在我的桌面上生成如下目录结构及R.java文件
1.1、第一小步,了解一下它的实现过程:
aapt工具的源码在android系统源码的frameworks\base\tools\aapt目录下,生成的过程主要是调用了aapt源码目录下Resource.cpp文件中的buildResources()函数,该函数首先检查AndroidManifest.xml的合法性,然后对res目录下的资源子目录进行处理,处理的函数为makeFileResources(),处理的内容包括资源文件名的合法性,向资源表table添加条目等,处理完后调用complieResourceFlie()函数编译res与asserts目录下的资源并生成resources.arsc文件,compileResourceFile()函数位于aapt源码目录的ResourceTable.cpp文件中,该函数最后会调用parseAndAddEntry()函数生成R.java文件,完成资源编译后,接下来调用compileXmlFile()函数对res目录的子目录下的xml文件分别进行编译,这样处理过得xml文件就简单的被“加密”过了,最后将所有的资源与编译生成的resources.arsc文件以及“加密”过的AndroidManifest.xml文件打包压缩成resources.ap_文件(使用ant工具命令行编译则会生成与buiild.xml中“project name”指定的属性同名的ap_文件)。资源文件中res/animator、res/anim、res/color、res/drawable(非Bitmap文件,即非.png、.9.png、.jpg、.gif文件)、res/layout、res/menu、res/values和res/xml的资源文件均会从文本格式的XML文件编译成二进制格式的XML文件,assets和res/raw原封不动打包进去。除了assets资源之外,其它的资源都会被赋予一个资源ID。
1.2、第二小步是处理aidl文件:
aidl -ID:/JavaTest/Demos/src D:/JavaTest/Demos/src/com/example/android/apis/app/IRemoteService.aidl
"-I"与"D:/JavaTest/***"之间是没有空格的。执行此条命令后,生成的.java会与.aidl文件在同一目录下。对于没有使用到aidl的Android工程,这一步可以跳过。这一步使用到的工具为aidl,位于android-sdk\platform-tools目录下,aidl工具解析接口定义文件(aidl为android interface definition language的首字母缩写,即android接口描述语言)并生成相应的java代码供程序调用,源码位于Android源码的frameworks\base\tools\aidl目录下。
1.3、第三小步通过javac命令将所有的.java文件编译成.class文件包括上边生成的R.java文件:
javac -target 1.8 -bootclasspath D:\lylsoft\android\androidstudio\sdk\platforms\android-24\android.jar -d C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo -sourcepath D:\lylsoft\android\androidstudio\opensource\MyButterKnifeDemo\app\src C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\R.java
执行完上述命令后在桌面上的文件夹中就会出现如下目录
参数含义如下:
-target
-bootclasspath <路径> 覆盖引导类文件的位值
-d <目录> 指定存放生成的类文件的位置
-sourcepath <路径> 指定查找输入源文件的位置(这里包含两个地方一个是src,一个是桌面gen生成的R.java)
1.4、第四小步就是生成.dex文件:
这个相对简单,主要用到SDK/build-tools中的dx工具。命令如下:
dx --dex --output=\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\classes.dex C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\com\demo\jason\mybutterknifedemo
--output=<要生成的classes.dex路径> <要处理的class文件的路径>
这样便会生成我们的.dex文件了,这里如果可能会报一个RuntimeException----ParseException这是由于我们引入的jar包编译环境(比如是1.8)要高于android中默认的jdk编译版本(我用的是1.7)。要解决这个问题,只要android项目的编译jdk版本要高于等于引入jar包的编译jdk版本,然后重新编译就好了。既然如此修改eclipse的jdk编译版本改为1.8就好了。
这样我们的第一步生成.dex文件就算是完成了
2.1、在这里就第二次用到了我们sdk中的aapt工具了,命令如下:
aapt package -f -M AndroidManifest.xml -S D:\lylsoft\android\androidstudio\opensource\MyButterKnifeDemo\app\src\main\res -I D:\lylsoft\android\androidstudio\sdk\platforms\android-24\android.jar -A D:\lylsoft\android\androidstudio\opensource\MyButterKnifeDemo\app\src\main -F C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\Resource.zip
参数介绍如下,跟生成R.java文件差不多:
-f 如果编译生成的文件已经存在,强制覆盖。
-m 使生成的包的目录存放在-J参数指定的目录
-S 指定res文件夹的路径
-I 指定某个版本平台的android.jar文件的路径
-A 指定assert文件夹的路径
-F 指定输出文件完整路径。
回车执行,我们的Resource.zip文件夹就算是生成了,这个名字是可以指定的。这一步比较简单,没有太多要说的。接着往下看。第三步就是生成我们的.apk文件了,这里的.apk文件是没有签名没有优化的.apk文件。
这里需要组合我们上边生成的两大文件,即.dex文件和Resource.zip文件。这里主要使用apkbuilder脚本,其实是个批处理文件,不过android 3.0后已经被删除,但网上还是可以找到这个脚本的,直接拷贝放到/SDK/tools下即可。其实里面执行的就是sdklib.jar。
apkbuilder C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\demo.apk -v -u -z C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\Resource.zip -f C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\classes.dex
参数含义如下:
第一个参数是存放打包后的文件完整路径
-v Verbose 显示过程信息
-u 创建一个无签名的包
-z 指定apk资源路径
-f 指定dex文件路径
回车执行:在我们指定的路径下就会生成我们没有优化签名的apk文件,如下图:
打包的工具为apkbuilder,它位于android-sdk\tools目录下,apkbuilder为一个脚本文件,实际调用的是android-sdk\tools\lib\sdklib.jar文件中的com.android.sdklib.build.apkbuilderMain类。它的实现代码位于android系统源码的sdk\sdkmanager\libs\sdklib\src\com\android\sdklib\build\akpbuilderMain.java文件,代码构建了一个apkbuilder类,然后以包含resources.arec的文件为基础生成apk文件,这个文件一般为ap_结尾的文件,接着调用addSourceFolder()函数添加的资源,addSourceFolder()会调用processFileForResource()函数往apk文件中添加资源,处理的内容包括res目录与assets目录中的文件,添加完整源后调用addResourcesFromJar()函数往apk文件中写入依赖库,接着调用addNativeLibraries()函数添加工程libs目录下的Native库(通过androidDNK编译生成的so或bin文件),最后条用sealApk()关闭apk文件。
接下来就是给我们的demo.apk进行签名了,用到了jarsigner命令,java的签名工具,如下:
jarsigner -verbose -keystore C:\Users\jason\Desktop\demo.keystore -storepass android -keypass android -signedjar C:\Users\jason\Desktop\com\demo\jason\mybutterknifedemo\demo.apk C:\Users\jason\Desktop\com\demo\jason\demo.apk demo1
各个参数的意义如下:
-verbose 签名/验证时输出详细信息
-keystore 密钥库路径
-storepass 用于密钥库完整性的口令(密码)
-keypass 专用密钥的口令(密码)
-signedjar 已签名的 apk 文件的名称 (第一个apk是签名之后的文件, 第二个apk是需要签名的文件)
回车执行,稍等片刻,在我们指定的文件架下就会生成我们已经打包好的apk文件了,这个已经是签名好的apk文件了。
下一步就是用zipalign对我们的apk进行优化了,我们可以通过zipalign对apk进行检测。如下:
zipalign -c -v 4 C:\Users\jason\Desktop\com\demo\jason\demo.apk
-c 表示进行zipalign优化检测
-v 输出过程信息
如果我们没有优化过我们的apk文件,则会认证失败,会看到Verification FAILED的相关信息。然后我们就可以优化我们的apk文件了。如下:
zipalign -f -v 4 C:\Users\jason\Desktop\com\demo\jason\demo.apk C:\Users\jason\Desktop\com\demo\jason\demo1.apk
-f 如果文件已经存在,强制覆盖
-v 输出详细信息
- 需要zipalign优化的apk 优化后的apk名称以及存放位置
运行完成后我们的指定文件架下就出现了我们已经优化好的apk文件了。如下图
我们这次执行我们的检测命令发现最后验证成功了(打印出Verification succesful)。
zipalign工具位于android-sdk\tools目录,源码位于android系统源码的build\tools\zipalign,它的主要工作是将apk包进行对齐处理,使apk包中的所有资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问Apk文件时的速度会更快,验证apk文件是否对齐过的工作由ZipAlign.cpp文件的verify()函数完成,处理对齐的工作则由process()函数完成。
好了以上就是我们用纯命令行的方式打包一个apk文件的过程,是不是感觉很麻烦,我们一般在开发者没人这么搞,写这么多,主要是自己屡一下打包逻辑,同时帮助大家总结一下,让大家都知道整个apk是如何创建成功并进行签名优化的,其中用到了哪些工具。