插件化-Apk編譯過程概述

插件化-Apk編譯過程概述

0x00

大致的看了一下目前插件化的開源實現,或多或少都會對Apk的編譯過程做出改動,因此嘗試分析了一下Apk的打包過程。一個Apk文件實際上就是一個zip壓縮包,我們把一個Apk解壓后,里面的內容類似下圖。

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

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

0x01 HelloWorld

下面我們來嘗試手動編譯一個最簡單的Apk。開始之前,先簡單介紹一下我們要使用的工具。

  1. aapt[Android Asset Packaging Tool]這個工具主要幫助我們處理資源文件,以及創建,更新,查看一個Apk文件。
  2. dx,這個工具幫助我們把.class文件轉換成一個dex文件,dex[Dalvik Executable]文件就是Dalvik虛擬機的可執行文件,這個文件的具體格式稍后會做簡單的介紹。
  3. zipalign,這個工具用來優化我們生成的apk文件,它將資源文件進行4字節對齊,當資源文件映射進內存時,對齊到4字節邊界可以加快資源文件的訪問速度。

還有兩個我們使用的工具并沒有出現這里,而是存在于JDK中。

  1. javac,很常用的工具,用來將java源碼文件編譯成字節碼文件
  2. keytool,創建簽名的工具
  3. jarsigner,用來對生成的apk進行簽名的工具

下面我們來開始編譯helloWorld,首先我們編寫了一個最簡單的Apk。

這個工程只有一個Activity,并且不依賴任何庫。

Step 1: 生成R.java文件

R.java是我們訪問資源的必需品,R文件是一個普通的類,其中根據資源的類型有不同的靜態內部類,每個靜態內部類中的靜態常量分別定義一條資源標示符,這個類并不是我們編寫的而是由aapt工具生成的。
在工程的根目錄,執行:

aapt package \   #打包資源文件
-f \             #強制覆蓋已有文件
-m \             #使R文件在-J參數指定的位置生成
-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來編譯我們的代碼了,繼續執行:

javac -classpath \                          #添加依賴包,多個jar包用:分割
$ANDROID_HOME/platforms/android-23/android.jar \ #sdk-23
-source 1.7 -target 1.7 \                        #指明源碼版本和字節碼版本
-d ./build \                                     #編譯后的class文件的路徑
./java/com/haizhi/oa/buildtest/*.java \          #源碼1,這是我們寫的Activity
./gen/com/haizhi/oa/buildtest/R.java             #源碼2,R.java

Step 3: 編譯為dex文件

在上一步中,我們將代碼編譯成了字節碼,但是dalvik并不能直接執行字節碼,需要進一步的將class文件編譯成dex文件,這個過程是通過dx這個工具實現的,在build目錄下,我們繼續執行:

dx --dex --output=classes.dex .   #指定輸出為classes.dex 輸入為當前目錄

至此,我們已經獲得了生成一個Apk需要的所有東西。

Step 4: 打包所有的資源文件

在工程的根目錄,執行:

aapt package \
-f \
-S res \
-I $ANDROID_HOME/platforms/android-23/android.jar \
-M AndroidManifest.xml \
-F test.apk.u                       #生成apk文件

此時,我們已經獲得了一個apk文件,下面我們要對它簽名,首先需要使用keytool工具生成一個簽名文件,這個步驟可以自行百度。

Step 5: 將classes.dex文件加入apk中

aapt add -f test.apk.u classes.dex

Step 6: 簽名,對齊
在工程的根目錄,執行:

簽名:
jarsigner -storepass **密*碼** -keystore ../chenlong.keystore test.apk.u chenlong

對齊:
zipalign 4 test.apk.u test.apk

經過上述5個步驟,我們生成了一個apk,下面安裝到模擬器上執行一下,如圖:

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

我們知道,java源碼文件編譯后生成了字節碼文件,然后被jvm執行,字節碼文件中有一個非常重要的區域是常量池,編譯的過程中,字節碼文件并不會保存方法和字段的最終內存布局信息,也就是說,方法和字段并不像C/C++那樣被編譯成地址,jvm在加載Class文件的時候,需要從常量池獲取對應的符號引用,再在類創建時或運行時解析并翻譯到具體的內存地址中【參考:深入理解Java虛擬機-JVM高級特性與最佳實踐】。一個字節碼文件中,除了方法體中的內容被編譯為字節碼指令外,大部分的信息都保存在常量池中,通過索引來訪問,包括類的名稱,類的字段,類的繼承關系,類中方法的定義等。

那么,dex文件和class文件有什么區別呢?

首先,dalvik虛擬機的字節碼指令是16位,而jvm是8位,因此,java 字節碼被轉換成dex 字節碼;其次,dex文件將多個class文件合并成一個,合并了這些class文件的常量池,并作出了其他的優化,讓dex文件執行的更快,更節省內存。對于dex文件的詳細格式,可以參考 dex-format,我嘗試了一下直接閱讀dex文件,講真,不是很好讀。。下圖是我們剛剛編譯出的dex文件的16進制格式,加了一些簡單的標注和分塊,一共3012個字節。

0x02 進階-編譯一個帶依賴的工程

在實際的編碼過程中,我們往往會去依賴一些子工程,子工程有兩種,一種是java工程,一種是Android Lib工程。java工程中不包含資源文件,編譯后的輸出是jar包,而Android Lib工程包含資源文件,編譯后的輸出為aar文件。

對于jar包,我們只需要在編譯apk的java代碼時,將jar包加入classpath,然后在編譯dex文件時,將jar包一起編譯進去就可以了,但是對于aar文件,就稍微有點復雜了。

首先,我們還是創建一個工程,如圖:

這個工程依賴了design包,v7包中的appcompat,同時,上述這些包又依賴了v4包,,recyclerview,support-vector-drawable,animated-vector-drawable,support-annotations。

上述這些依賴都是Android Lib工程,因此我們需要處理依賴包中的資源。首先,我們需要這些依賴的aar文件作為輸入,到哪里去找aar文件呢?最初,我在sdk下找到了這些依賴的jar包和相應的資源目錄,但是,當我嘗試編譯的時候,總是提示我找不到資源,我很苦惱,后來在高旭大神的指點下,我看了一下gradle的實現方式,發現gradle并不使用jar包+資源來重新編譯這些依賴庫而是直接使用了google提供的這些依賴庫的aar文件,于是我嘗試將編譯好的aar文件解包,再使用解包后的資源和jar包進行編譯。

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 參數用來加載多個資源目錄,按照從左向右的順序,如果后面的資源重復則跳過,如果不重復則新增。
--extra-packages用來對不同的資源目錄生成包名不同的R文件,多個包名通過:分割。

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

后面的簽名、對齊操作和之前一樣

最后,我們在模擬器上運行一下打包后的apk文件,如圖:

0x03 總結

編譯流程的簡單分析就是這些,在上述流程中我們可以看到,主要過程是資源處理和dex文件生成上,其中對資源的處理是插件化的一個難點,我的分析并不是很全面,比如對于多個資源目錄合并的過程,aapt自身提供的機制和gradle的實現就不太一樣,gradle在最終調用aapt之前已經將資源合并,傳入aapt的只有一個合并后的資源目錄,可以參考gradle 資源合并機制,后續我會針對資源文件的處理做單獨的分析。

上述內容如有錯誤,懇請指正,我會繼續分析插件化的相關技術實現,敬請期待。


原文:插件化-Apk編譯過程概述

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容