插件化-Apk編譯過程概述
0x00
大致的看了一下目前插件化的開源實(shí)現(xiàn)验毡,或多或少都會(huì)對(duì)Apk的編譯過程做出改動(dòng)称龙,因此嘗試分析了一下Apk的打包過程识啦。一個(gè)Apk文件實(shí)際上就是一個(gè)zip壓縮包伴栓,我們把一個(gè)Apk解壓后诬辈,里面的內(nèi)容類似下圖混弥。
里面每個(gè)文件是什么含義趴乡,我們待會(huì)再看对省。那么,如何生成一個(gè)Apk文件呢晾捏?通常情況下蒿涎,我們使用一些構(gòu)建工具來(lái)編譯我們的工程,例如古老的Ant惦辛,maven劳秋,我們正在使用的gradle,以及更加黑科技的buck等胖齐,但是玻淑,這些構(gòu)建工具并不直接作用于編譯過程,打開sdk中的build-tools目錄呀伙,如下
這些就是google為我們提供的工具补履,通過它們,我們得以將代碼編譯成Apk剿另,構(gòu)建工具只是這些的工具的封裝箫锤。
0x01 HelloWorld
下面我們來(lái)嘗試手動(dòng)編譯一個(gè)最簡(jiǎn)單的Apk。開始之前雨女,先簡(jiǎn)單介紹一下我們要使用的工具谚攒。
- aapt[Android Asset Packaging Tool]這個(gè)工具主要幫助我們處理資源文件,以及創(chuàng)建氛堕,更新馏臭,查看一個(gè)Apk文件。
- dx岔擂,這個(gè)工具幫助我們把.class文件轉(zhuǎn)換成一個(gè)dex文件位喂,dex[Dalvik Executable]文件就是Dalvik虛擬機(jī)的可執(zhí)行文件,這個(gè)文件的具體格式稍后會(huì)做簡(jiǎn)單的介紹乱灵。
- zipalign塑崖,這個(gè)工具用來(lái)優(yōu)化我們生成的apk文件,它將資源文件進(jìn)行4字節(jié)對(duì)齊痛倚,當(dāng)資源文件映射進(jìn)內(nèi)存時(shí)规婆,對(duì)齊到4字節(jié)邊界可以加快資源文件的訪問速度。
還有兩個(gè)我們使用的工具并沒有出現(xiàn)這里蝉稳,而是存在于JDK中抒蚜。
- javac,很常用的工具,用來(lái)將java源碼文件編譯成字節(jié)碼文件
- keytool耘戚,創(chuàng)建簽名的工具
- jarsigner嗡髓,用來(lái)對(duì)生成的apk進(jìn)行簽名的工具
下面我們來(lái)開始編譯helloWorld,首先我們編寫了一個(gè)最簡(jiǎn)單的Apk收津。
這個(gè)工程只有一個(gè)Activity,并且不依賴任何庫(kù)饿这。
Step 1: 生成R.java文件
R.java是我們?cè)L問資源的必需品浊伙,R文件是一個(gè)普通的類,其中根據(jù)資源的類型有不同的靜態(tài)內(nèi)部類长捧,每個(gè)靜態(tài)內(nèi)部類中的靜態(tài)常量分別定義一條資源標(biāo)示符嚣鄙,這個(gè)類并不是我們編寫的而是由aapt工具生成的。
在工程的根目錄串结,執(zhí)行:
aapt package \ #打包資源文件
-f \ #強(qiáng)制覆蓋已有文件
-m \ #使R文件在-J參數(shù)指定的位置生成
-S res \ #資源目錄
-J gen \ #R.java的位置
-I $ANDROID_HOME/platforms/android-23/android.jar \ #base-package
-M AndroidManifest.xml #清單文件的路徑
Step 2: 編譯代碼
生成后的文件存放在gen目錄下哑子,有了R.java 我們就可以使用javac來(lái)編譯我們的代碼了,繼續(xù)執(zhí)行:
javac -classpath \ #添加依賴包肌割,多個(gè)jar包用:分割
$ANDROID_HOME/platforms/android-23/android.jar \ #sdk-23
-source 1.7 -target 1.7 \ #指明源碼版本和字節(jié)碼版本
-d ./build \ #編譯后的class文件的路徑
./java/com/haizhi/oa/buildtest/*.java \ #源碼1卧蜓,這是我們寫的Activity
./gen/com/haizhi/oa/buildtest/R.java #源碼2,R.java
Step 3: 編譯為dex文件
在上一步中声功,我們將代碼編譯成了字節(jié)碼烦却,但是dalvik并不能直接執(zhí)行字節(jié)碼宠叼,需要進(jìn)一步的將class文件編譯成dex文件先巴,這個(gè)過程是通過dx
這個(gè)工具實(shí)現(xiàn)的,在build目錄下冒冬,我們繼續(xù)執(zhí)行:
dx --dex --output=classes.dex . #指定輸出為classes.dex 輸入為當(dāng)前目錄
至此伸蚯,我們已經(jīng)獲得了生成一個(gè)Apk需要的所有東西。
Step 4: 打包所有的資源文件
在工程的根目錄简烤,執(zhí)行:
aapt package \
-f \
-S res \
-I $ANDROID_HOME/platforms/android-23/android.jar \
-M AndroidManifest.xml \
-F test.apk.u #生成apk文件
此時(shí)剂邮,我們已經(jīng)獲得了一個(gè)apk文件,下面我們要對(duì)它簽名横侦,首先需要使用keytool工具生成一個(gè)簽名文件挥萌,這個(gè)步驟可以自行百度。
Step 5: 將classes.dex文件加入apk中
aapt add -f test.apk.u classes.dex
Step 6: 簽名枉侧,對(duì)齊
在工程的根目錄引瀑,執(zhí)行:
簽名:
jarsigner -storepass **密*碼** -keystore ../chenlong.keystore test.apk.u chenlong
對(duì)齊:
zipalign 4 test.apk.u test.apk
經(jīng)過上述5個(gè)步驟,我們生成了一個(gè)apk榨馁,下面安裝到模擬器上執(zhí)行一下憨栽,如圖:
以上,就是一個(gè)最簡(jiǎn)單的Apk的編譯過程翼虫,其中Apk最重要的兩個(gè)部分屑柔,資源和代碼被編譯成了resources.arsc+res以及dex文件。res是實(shí)際的資源珍剑,resources.arsc則是一個(gè)索引掸宛,AssetManager通過這個(gè)索引獲取資源的實(shí)際內(nèi)容,這其中的過程比較復(fù)雜招拙,暫時(shí)還沒有太多的分析唧瘾,至于dex文件翔曲,倒是可以啰嗦兩句。
我們知道劈愚,java源碼文件編譯后生成了字節(jié)碼文件瞳遍,然后被jvm執(zhí)行,字節(jié)碼文件中有一個(gè)非常重要的區(qū)域是常量池菌羽,編譯的過程中掠械,字節(jié)碼文件并不會(huì)保存方法和字段的最終內(nèi)存布局信息,也就是說注祖,方法和字段并不像C/C++那樣被編譯成地址猾蒂,jvm在加載Class文件的時(shí)候,需要從常量池獲取對(duì)應(yīng)的符號(hào)引用是晨,再在類創(chuàng)建時(shí)或運(yùn)行時(shí)解析并翻譯到具體的內(nèi)存地址中【參考:深入理解Java虛擬機(jī)-JVM高級(jí)特性與最佳實(shí)踐】肚菠。一個(gè)字節(jié)碼文件中,除了方法體中的內(nèi)容被編譯為字節(jié)碼指令外罩缴,大部分的信息都保存在常量池中蚊逢,通過索引來(lái)訪問,包括類的名稱箫章,類的字段烙荷,類的繼承關(guān)系,類中方法的定義等檬寂。
那么终抽,dex文件和class文件有什么區(qū)別呢?
首先桶至,dalvik虛擬機(jī)的字節(jié)碼指令是16位昼伴,而jvm是8位,因此镣屹,java 字節(jié)碼被轉(zhuǎn)換成dex 字節(jié)碼圃郊;其次,dex文件將多個(gè)class文件合并成一個(gè)野瘦,合并了這些class文件的常量池描沟,并作出了其他的優(yōu)化,讓dex文件執(zhí)行的更快鞭光,更節(jié)省內(nèi)存吏廉。對(duì)于dex文件的詳細(xì)格式,可以參考 dex-format惰许,我嘗試了一下直接閱讀dex文件席覆,講真,不是很好讀汹买。佩伤。下圖是我們剛剛編譯出的dex文件的16進(jìn)制格式聊倔,加了一些簡(jiǎn)單的標(biāo)注和分塊,一共3012個(gè)字節(jié)生巡。
0x02 進(jìn)階-編譯一個(gè)帶依賴的工程
在實(shí)際的編碼過程中耙蔑,我們往往會(huì)去依賴一些子工程,子工程有兩種孤荣,一種是java工程甸陌,一種是Android Lib工程。java工程中不包含資源文件盐股,編譯后的輸出是jar包钱豁,而Android Lib工程包含資源文件,編譯后的輸出為aar文件疯汁。
對(duì)于jar包牲尺,我們只需要在編譯apk的java代碼時(shí),將jar包加入classpath幌蚊,然后在編譯dex文件時(shí)谤碳,將jar包一起編譯進(jìn)去就可以了,但是對(duì)于aar文件霹肝,就稍微有點(diǎn)復(fù)雜了估蹄。
首先,我們還是創(chuàng)建一個(gè)工程沫换,如圖:
這個(gè)工程依賴了design包,v7包中的appcompat最铁,同時(shí)讯赏,上述這些包又依賴了v4包,冷尉,recyclerview漱挎,support-vector-drawable,animated-vector-drawable雀哨,support-annotations磕谅。
上述這些依賴都是Android Lib工程,因此我們需要處理依賴包中的資源雾棺。首先膊夹,我們需要這些依賴的aar文件作為輸入,到哪里去找aar文件呢捌浩?最初放刨,我在sdk下找到了這些依賴的jar包和相應(yīng)的資源目錄,但是尸饺,當(dāng)我嘗試編譯的時(shí)候进统,總是提示我找不到資源助币,我很苦惱,后來(lái)在高旭大神的指點(diǎn)下螟碎,我看了一下gradle的實(shí)現(xiàn)方式眉菱,發(fā)現(xiàn)gradle并不使用jar包+資源來(lái)重新編譯這些依賴庫(kù)而是直接使用了google提供的這些依賴庫(kù)的aar文件,于是我嘗試將編譯好的aar文件解包掉分,再使用解包后的資源和jar包進(jìn)行編譯倍谜。
Step 1: 生成R.java文件
aapt package -f -m --auto-add-overlay \
-S res \
-S /Users/chenlong/sdk/extras/android/m2repository/com/android/support/appcompat-v7/23.3.0/aarEx/res \
-S /Users/chenlong/sdk/extras/android/m2repository/com/android/support/recyclerview-v7/23.3.0/aarEx/res \
-S /Users/chenlong/sdk/extras/android/m2repository/com/android/support/design/23.3.0/aarEx/res \
-J gen \
--extra-packages android.support.v7.appcompat:android.support.v7.recyclerview:android.support.design \
-I $ANDROID_HOME/platforms/android-23/android.jar -M ./AndroidManifest.xml
其中,--auto-add-overlay
參數(shù)用來(lái)加載多個(gè)資源目錄叉抡,按照從左向右的順序尔崔,如果后面的資源重復(fù)則跳過,如果不重復(fù)則新增褥民。
--extra-packages
用來(lái)對(duì)不同的資源目錄生成包名不同的R文件季春,多個(gè)包名通過:
分割。
Step 2: 編譯代碼
javac -classpath $ANDROID_HOME/extras/android/support/v7/appcompat/libs/android-support-v4.jar:\
$ANDORID_HOME/extras/android/support/annotations/android-support-annotations.jar:\
$ANDROID_HOME/platforms/android-23/android.jar:\
$ANDROID_HOME/extras/android/support/design/libs/android-support-design.jar:\
$ANDROID_HOME/extras/android/support/v7/appcompat/libs/android-support-v7-appcompat.jar:\
$ANDROID_HOME/extras/android/support/v7/recyclerview/libs/android-support-v7-recyclerview.jar \
-source 1.7 -target 1.7 \
-d ./build \
./java/com/haizhi/oa/buildtest/*.java \
./gen/com/haizhi/oa/buildtest/R.java \
./gen/android/support/design/R.java \
./gen/android/support/v7/appcompat/R.java \
./gen/android/support/v7/recyclerview/R.java
Step 3: 編譯dex文件
dx --dex --output=classes.dex . \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/support-v4/23.3.0/aarEx/classes.jar \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/support-v4/23.3.0/aarEx/libs/internal_impl-23.3.0.jar \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/design/23.3.0/aarEx/classes.jar \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/appcompat-v7/23.3.0/aarEx/classes.jar \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/recyclerview-v7/23.3.0/aarEx/classes.jar \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/support-vector-drawable/23.3.0/aarEx/classes.jar \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/animated-vector-drawable/23.3.0/aarEx/classes.jar \
/Users/chenlong/sdk/extras/android/m2repository/com/android/support/support-annotations/23.3.0/support-annotations-23.3.0.jar
Step 4: 生成apk文件
aapt package -f -m --auto-add-overlay \
-S res \
-S /Users/chenlong/sdk/extras/android/m2repository/com/android/support/appcompat-v7/23.3.0/aarEx/res \
-S /Users/chenlong/sdk/extras/android/m2repository/com/android/support/recyclerview-v7/23.3.0/aarEx/res \
-S /Users/chenlong/sdk/extras/android/m2repository/com/android/support/design/23.3.0/aarEx/res \
-I $ANDROID_HOME/platforms/android-23/android.jar -M ./AndroidManifest.xml \
-F test.apk.u
Step 5: 將classes.dex文件加入apk中
aapt add -f test.apk.u classes.dex
后面的簽名消返、對(duì)齊操作和之前一樣
最后载弄,我們?cè)谀M器上運(yùn)行一下打包后的apk文件,如圖:
0x03 總結(jié)
編譯流程的簡(jiǎn)單分析就是這些撵颊,在上述流程中我們可以看到宇攻,主要過程是資源處理和dex文件生成上,其中對(duì)資源的處理是插件化的一個(gè)難點(diǎn)倡勇,我的分析并不是很全面逞刷,比如對(duì)于多個(gè)資源目錄合并的過程,aapt自身提供的機(jī)制和gradle的實(shí)現(xiàn)就不太一樣妻熊,gradle在最終調(diào)用aapt之前已經(jīng)將資源合并夸浅,傳入aapt的只有一個(gè)合并后的資源目錄,可以參考gradle 資源合并機(jī)制扔役,后續(xù)我會(huì)針對(duì)資源文件的處理做單獨(dú)的分析帆喇。
上述內(nèi)容如有錯(cuò)誤,懇請(qǐng)指正亿胸,我會(huì)繼續(xù)分析插件化的相關(guān)技術(shù)實(shí)現(xiàn)坯钦,敬請(qǐng)期待。