插件化-Apk編譯過(guò)程概述

插件化-Apk編譯過(guò)程概述

0x00

大致的看了一下目前插件化的開源實(shí)現(xiàn),或多或少都會(huì)對(duì)Apk的編譯過(guò)程做出改動(dòng),因此嘗試分析了一下Apk的打包過(guò)程。一個(gè)Apk文件實(shí)際上就是一個(gè)zip壓縮包,我們把一個(gè)Apk解壓后,里面的內(nèi)容類似下圖。

里面每個(gè)文件是什么含義,我們待會(huì)再看。那么,如何生成一個(gè)Apk文件呢?通常情況下,我們使用一些構(gòu)建工具來(lái)編譯我們的工程,例如古老的Ant,maven,我們正在使用的gradle,以及更加黑科技的buck等,但是,這些構(gòu)建工具并不直接作用于編譯過(guò)程,打開sdk中的build-tools目錄,如下

這些就是google為我們提供的工具,通過(guò)它們,我們得以將代碼編譯成Apk,構(gòu)建工具只是這些的工具的封裝。

0x01 HelloWorld

下面我們來(lái)嘗試手動(dòng)編譯一個(gè)最簡(jiǎn)單的Apk。開始之前,先簡(jiǎn)單介紹一下我們要使用的工具。

  1. aapt[Android Asset Packaging Tool]這個(gè)工具主要幫助我們處理資源文件,以及創(chuàng)建,更新,查看一個(gè)Apk文件。
  2. dx,這個(gè)工具幫助我們把.class文件轉(zhuǎn)換成一個(gè)dex文件,dex[Dalvik Executable]文件就是Dalvik虛擬機(jī)的可執(zhí)行文件,這個(gè)文件的具體格式稍后會(huì)做簡(jiǎn)單的介紹。
  3. zipalign,這個(gè)工具用來(lái)優(yōu)化我們生成的apk文件,它將資源文件進(jìn)行4字節(jié)對(duì)齊,當(dāng)資源文件映射進(jìn)內(nèi)存時(shí),對(duì)齊到4字節(jié)邊界可以加快資源文件的訪問(wèn)速度。

還有兩個(gè)我們使用的工具并沒有出現(xiàn)這里,而是存在于JDK中。

  1. javac,很常用的工具,用來(lái)將java源碼文件編譯成字節(jié)碼文件
  2. keytool,創(chuàng)建簽名的工具
  3. 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問(wèn)資源的必需品,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è)過(guò)程是通過(guò)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)過(guò)上述5個(gè)步驟,我們生成了一個(gè)apk,下面安裝到模擬器上執(zhí)行一下,如圖:

以上,就是一個(gè)最簡(jiǎn)單的Apk的編譯過(guò)程,其中Apk最重要的兩個(gè)部分,資源和代碼被編譯成了resources.arsc+res以及dex文件。res是實(shí)際的資源,resources.arsc則是一個(gè)索引,AssetManager通過(guò)這個(gè)索引獲取資源的實(shí)際內(nèi)容,這其中的過(guò)程比較復(fù)雜,暫時(shí)還沒有太多的分析,至于dex文件,倒是可以啰嗦兩句。

我們知道,java源碼文件編譯后生成了字節(jié)碼文件,然后被jvm執(zhí)行,字節(jié)碼文件中有一個(gè)非常重要的區(qū)域是常量池,編譯的過(guò)程中,字節(jié)碼文件并不會(huì)保存方法和字段的最終內(nèi)存布局信息,也就是說(shuō),方法和字段并不像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é)碼指令外,大部分的信息都保存在常量池中,通過(guò)索引來(lái)訪問(wèn),包括類的名稱,類的字段,類的繼承關(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í)際的編碼過(guò)程中,我們往往會(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ù)則跳過(guò),如果不重復(fù)則新增。
--extra-packages用來(lái)對(duì)不同的資源目錄生成包名不同的R文件,多個(gè)包名通過(guò):分割。

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)單分析就是這些,在上述流程中我們可以看到,主要過(guò)程是資源處理和dex文件生成上,其中對(duì)資源的處理是插件化的一個(gè)難點(diǎn),我的分析并不是很全面,比如對(duì)于多個(gè)資源目錄合并的過(guò)程,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)期待。


原文:插件化-Apk編譯過(guò)程概述

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容