從開始著手公司app安卓原生版本的開發(fā),到如今2.3發(fā)布,已經(jīng)過了快半年的時間,在這半年的時間里,已經(jīng)逐漸掌握了Android 打包的一些基礎(chǔ)知識。今天在這里小小梳理一下,順便總結(jié)下安卓打包中需要注意的問題和一些有效的經(jīng)驗(yàn)。
打包過程介紹
首先,需要注意的是不管是打什么包,或者用什么工具打包,其背后都是執(zhí)行的Android提供的構(gòu)建系統(tǒng)。所以我們先從每個過程介紹一下Android Build系統(tǒng)是如何一步一步將 java文件、資源文件、第三方庫等打包成APK的。

資源文件的預(yù)編譯
打包過程的第一步,便是資源文件的預(yù)編譯,資源文件包括所有位于res/目錄下的所有文件以及所有的AndroidManifest文件,在這一步中Android使用aapt工具將資源文件進(jìn)行編譯,生成R文件。R文件中每個ID的生成都有一個特殊的規(guī)則,每個id都是一個四字節(jié)的數(shù)字 0x PPTTNNNN, 第一個字節(jié)PP代表了資源文件代表的包名,在Android應(yīng)用程序中PP始終是7f。第二個字節(jié)TT代表了資源文件的類型,aapt從0開始每發(fā)現(xiàn)一種新的資源類型就加1,最后面兩個字節(jié)NNNN代表了資源文件在該類型中的順序索引,跟TT類似,從0開始每發(fā)現(xiàn)一個新的資源文件名就加1。比如我們看一下我們工程中生成的R文件


我們可以使用appt命令輸出打包好的apk中所有的資源文件列表
appt dump resources <path-to-apk>
AIDL文件編譯
AIDL 全稱Android Interface definition language。在Android如果涉及到多進(jìn)程比如遠(yuǎn)程service的時候就需要去定義aidl文件,用來統(tǒng)一描述進(jìn)行間通信的接口。
interface IMyAidlInterface {
/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString);
}
Android打包時會將aidl文件轉(zhuǎn)化為一個同名的java接口,如下圖所以,可以在/build/generated/source/aidl下看到。

Java文件的編譯
這一步是標(biāo)準(zhǔn)的java文件編譯的過程,編譯的輸入既包括我們手動編寫的代碼也包括前兩步中生成java文件。編譯的輸出為.class文件。
生成.dex文件
第四步將上一步生成的class文件,以及引用的第三方庫中的class文件一起轉(zhuǎn)化為Dalvik字節(jié)碼。
雖然Android使用Java語言編程。但是Android并不使用標(biāo)準(zhǔn)的Java虛擬機(jī)進(jìn)行java code。Android有自己的虛擬機(jī) Dalvik以及5.0以上的ART。 可以大致了解一下Dalvik虛擬機(jī)和傳統(tǒng)虛擬機(jī)的區(qū)別:
- Dalvik虛擬機(jī)占用內(nèi)存更少
- JVM是基于Stack的(Stack based),DVM是基于Register的(Register based). 跟進(jìn)一步說,JVM將局部變量存在stack中,而DVM將變量保存在register中。因此標(biāo)準(zhǔn)java虛擬機(jī)需要更多的指令集,而DVM的指令集更少,另一方面DVM需要用寄存器編碼指令的source 和 destination ,因此Dalvik的一條指令更長。
- 在JVM運(yùn)行時,會根據(jù)需要去load每個class文件。而Dalvik中所有的class都在一個dex文件中。
基于第三條區(qū)別,我們在前第三步中生成的所有class文件以及第三庫中引用的class文件都需要一起整合為一個dex文件。

不過這里有一個潛在的危險(xiǎn),由于dex設(shè)計(jì)上的原因,單個dex內(nèi)最多能引用的方法數(shù)上限為65536,這個數(shù)字對于當(dāng)今很多APP來說都已經(jīng)不夠用了,不行的是,我們項(xiàng)目中就碰到了。不過Google也早早就放出了解決方案。這里就不詳細(xì)討論了,有興趣了解可以戳這里。
資源文件打包
第一步資源文件已經(jīng)使用aapt進(jìn)行了預(yù)編譯,是為了 讓所有引用了資源的java文件正常編譯,這里在打包之前,Android build system需要將所有的資源文件進(jìn)行打包。這里依然是使用第一步中用到的aapt工具。打包后會生成一個resources-debug.ap_文件,這個文件中包含了圖片,layout,menu,anim, AndroidManifest,其中圖片都經(jīng)過了優(yōu)化,xml文件被編譯成二進(jìn)制格式。string被編譯成單獨(dú)的resources.arsc文件。
打包APK
第五步就是打包APK了,這一步的工作就是將dex文件,資源文件(編譯的和未編譯的)打包在一塊,生成apk。
簽名
第五步生成的apk文件,必須經(jīng)過簽名才能安裝在設(shè)備上。簽名前,我們必須生成自己的keystore
$ keytool -genkey -v -keystore my-release-key.keystore-alias alias_name -keyalg RSA -keysize 2048 -validity 10000
使用keytool,生成一個keystore,這個keystore包含一個有效期10000天的key。
要對apk進(jìn)行簽名,需要用到j(luò)ava提供的簽名工具
$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1-keystore my-release-key.keystore my_application.apk alias_name
如果需要驗(yàn)證某個apk是否簽名過,可以使用
jarsigner -verify -verbose -certs my_application.apk
對齊
打包apk的最后一步是一個對齊工作。這一步的工具簡而言之就是使apk內(nèi)所有未壓縮的所有文件相對于apk的其實(shí)位置都是4字節(jié)對齊的。這樣,這些文件既可以直接使用mmap映射訪問。這樣做的目的是為了減少app在運(yùn)行時所占用的內(nèi)存。
zipalign -c -v 4 application.apk