項目的構建:當我們打開一個項目,我們可以看到的是我們寫的Java Code文件or Other JVM Code,資源文件,Build配置文件,但是通過run the project,我們就可以得到一個在我們的Andoid設備上可以運行的Apk,上線應用市場,還需要我們對其進行簽名處理,來確保我們App的唯一性和安全性。整個過程就是所謂的項目構建。如下圖所示。
上圖是Android官方提供的打包簡略流程圖。清晰地展示了一個Android Project經(jīng)過編譯和打包后生成apk文件,然后再經(jīng)過簽名,就可以安裝到設備上
我們將一個實際的apk文件后綴改為zip并解壓后,得到的內(nèi)容如下
和上圖的描述一致。apk包內(nèi)容包括:
classes.dex…
resources.arsc
assets
res
AndroidManifest.xml
META-INF
其中:
res中圖片和raw文件下內(nèi)容保持原樣,res中其他xml文件內(nèi)容均轉化為二進制形式;assets文件內(nèi)容保持原樣
res中的文件會被映射到R.java文件中,訪問的時候直接使用資源ID即R.id.filename;assets文件夾下的文件不會被映射到R.java中,訪問的時候需要AssetManager類
在看下這張圖 ,android apk構建詳細流程圖
如果你接觸Android開發(fā)已經(jīng)有一段時間了,我想當你看到這張圖的時候,就會覺得很清晰。但是更多的可能會一頭霧水,如果之前沒有閱讀相關的資料的話,那么,接下來,將針對上述的構建過程,先給出一個概述,這樣你將會整個構建流程在心中有一個框架,然后針對其中具體的細節(jié),進行進一步詳細的講解。
圖中綠色標注為其中用到的相應工具,藍色代表的是中間生成的各類文件類型。
首先aapt工具會將資源文件進行轉化,生成對應資源ID的R文件和資源文件。
adil工具會將其中的aidl接口轉化成Java的接口
至此,Java Compiler開始進行Java文件向class文件的轉化,將R文件,Java源代碼,由aidl轉化來的Java接口,統(tǒng)一轉化成.class文件。
通過dx工具將class文件轉化為dex文件。
此時我們得到了經(jīng)過處理后的資源文件和一個dex文件,當然,還會存在一些其它的資源文件,這個時候,就是將其打包成一個類似apk的文件。但還并不是直接可以安裝在Android系統(tǒng)上的APK文件。
通過簽名工具對其進行簽名。
通過Zipalign進行優(yōu)化,提升運行速度(原理后文會提及)。
最終,一個可以安裝在我們手機上的APK了。
通過上述講解,我想對于Android項目的整個構建過程,應該有了一個很清晰的框架了,下面將針對其中的具體的細節(jié),和前面挖的一些坑,來進行更細致的分析,下圖是一個Android項目構建過程的詳細步驟圖。
接下來的分析,我們還是按照上述構建過程概述的順序和流程,進行具體的分析。
第1步:aapt打包資源文件,生成R.java和編譯后的資源(二進制文件)
講到資源文件的處理,我們先來看一下Android中的資源文件有那些呢?Android應用程序資源可以分為兩大類,分別是assets和res:
1. assets類資源放在工程根目錄的assets子目錄下,它里面保存的是一些原始的文件,可以以任何方式來進行組織。這些文件最終會被原裝不動地打包在apk文件中。如果我們要在程序中訪問這些文件,那么就需要指定文件名來訪問。例如,假設在assets目錄下有一個名稱為filename的文件,那么就可以使用以下代碼來訪問它:
AssetManager am= getAssets();
InputStream is = assset.open("filename");
2. res類資源放在工程根目錄的res子目錄下,它里面保存的文件大多數(shù)都會被編譯,并且都會被賦予資源ID。這樣我們就可以在程序中通過ID來訪問res類的資源。res類資源按照不同的用途可以進一步劃分為以下10種子類型:
layout(布局文件),drawable,xml,value,menu,raw,color,anim,animator,mipmap。
為了使得一個應用程序能夠在運行時同時支持不同的大小和密度的屏幕,以及支持國際化,即支持不同的國家地區(qū)和語言,Android應用程序資源的組織方式有18個維度,每一個維度都代表一個配置信息,從而可以使得應用程序能夠根據(jù)設備的當前配置信息來找到最匹配的資源來展現(xiàn)在UI上,從而提高用戶體驗。由于Android應用程序資源的組織方式可以達到18個維度,因此就要求Android資源管理框架能夠快速定位最匹配設備當前配置信息的資源來展現(xiàn)在UI上,否則的話,就會影響用戶體驗。為了支持Android資源管理框架快速定位最匹配資源,Android資源打包工具aapt在編譯和打包資源的過程中,會執(zhí)行以下兩個額外的操作:
賦予每一個非assets資源一個ID值,這些ID值以常量的形式定義在一個R.java文件中。
生成一個resources.arsc文件,用來描述那些具有ID值的資源的配置信息,它的內(nèi)容就相當于是一個資源索引表。包含了所有的id值的數(shù)據(jù)集合。在該文件中,如果某個id對應的是string,那么該文件會直接包含該值,如果id對應的資源是某個layout或者drawable資源,那么該文件會存入對應資源的路徑。
為什么要轉化為二進制文件?
二進制格式的XML文件占用空間更小。這是由于所有XML元素的標簽、屬性名稱、屬性值和內(nèi)容所涉及到的字符串都會被統(tǒng)一收集到一個字符串資源池中去,并且會去重。有了這個字符串資源池,原來使用字符串的地方就會被替換成一個索引到字符串資源池的整數(shù)值,從而可以減少文件的大小。
二進制格式的XML文件解析速度更快。這是由于二進制格式的XML元素里面不再包含有字符串值,因此就避免了進行字符串解析,從而提高速度。
有了資源ID以及資源索引表之后,Android資源管理框架就可以迅速將根據(jù)設備當前配置信息來定位最匹配的資源了。
對于具體的一些操作流程,可以參考本人之前的一篇文章APK打包安裝過程或者更偏向于源碼層級的老羅的文章。(文后參考文獻鏈接)
第2步:aidl
aidl,全名Android Interface Definition Language,即Android接口定義語言。是我們在編寫進程間通信的代碼的時候,定義的接口。
輸入:aidl后綴的文件。輸出:可用于進程通信的C/S端java代碼,位于build/generated/source/aidl。
第3步:Java源碼編譯
我們有了R.java和aidl生成的Java文件,再加上工程的源代碼,現(xiàn)在可以使用javac進行正常的java編譯生成class文件了。
輸入:java source的文件夾(另外還包括了build/generated下的:R.java, aidl生成的java文件,以及BuildConfig.java)。輸出:對于gradle編譯,可以在build/intermediates/classes里,看到輸出的class文件。
第4步:代碼混淆(proguard)
源碼編譯之后,我們可能還會對其進行代碼的混淆,混淆的作用是增加反編譯的難度,同時也將一些代碼的命名進行了縮短,減少代碼占用的空間?;煜瓿芍螅瑫梢粋€混淆前后的映射表,這個是用來在反應我們的應用執(zhí)行的時候的一些堆棧信息,可以將混淆后的信息轉化為我們混淆前實際代碼中的內(nèi)容。
而這個過程使用的工具就是ProGuard,是一個開源的Java代碼混淆器(obfuscation)。ADT r8開始它被默認集成到了Android SDK中。 其具備三個主要功能。
壓縮 - 移除無效的類、屬性、方法等
優(yōu)化 - 優(yōu)化bytecode移除沒用的結構
混淆 - 把類名、屬性名、方法名替換為晦澀難懂的1到2個字母的名字
當然它也只能混淆Java代碼,Android工程中Native代碼,資源文件(圖片、xml),它是無法混淆的。而且對于Java的常量值也是無法混淆的,所以不要使用常量定義平文的密碼等重要信息。同時對于混淆,我們可以通過代碼制定去混淆那些,不去混淆那些。
-keep public class com.rensanning.example.Test
第5步:轉化為dex
調(diào)用dx.bat將所有的class文件轉化為classes.dex文件,dx會將class轉換為Dalvik字節(jié)碼,生成常量池,消除冗余數(shù)據(jù)等。由于dalvik是一種針對嵌入式設備而特殊設計的java虛擬機,所以dex文件與標準的class文件在結構設計上有著本質(zhì)的區(qū)別,當java程序編譯成class后,使用dx工具將所有的class文件整合到一個dex文件,目的是其中各個類能夠共享數(shù)據(jù),在一定程度上降低了冗余,同時也是文件結構更加經(jīng)湊,實驗表明,dex文件是傳統(tǒng)jar文件大小的50%左右。class文件結構和dex文件結構比對。
第6步:apkbuilder
打包生成APK文件。舊的apkbuilder腳本已經(jīng)廢棄,現(xiàn)在都已經(jīng)通過sdklib.jar的ApkBuilder類進行打包了。輸入為我們之前生成的包含resources.arcs的.ap_文件,上一步生成的dex文件,以及其他資源如jni、.so文件。
大致步驟為
以包含resources.arcs的.ap_文件為基礎,new一個ApkBuilder,設置debugMode
apkBuilder.addZipFile(f);
apkBuilder.addSourceFolder(f);
apkBuilder.addResourcesFromJar(f);
apkBuilder.addNativeLibraries(nativeFileList);
apkBuilder.sealApk(); // 關閉apk文件
generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath());
第7步:對APK簽名
對APK文件進行簽名。Android系統(tǒng)在安裝APK的時候,首先會檢驗APK的簽名,如果發(fā)現(xiàn)簽名文件不存在或者校驗簽名失敗,則會拒絕安裝,所以應用程序在發(fā)布之前一定要進行簽名。簽名信息中包含有開發(fā)者信息,在一定程度上可以防止應用被偽造。對一個APK文件簽名之后,APK文件根目錄下會增加META-INF目錄,該目錄下增加三個文件:
MANIFEST.MF
NETEASE.RSA
NETEASE.SF
Android系統(tǒng)就是根據(jù)這三個文件的內(nèi)容對APK文件進行簽名檢驗的。簽名過程主要利用apksign.jar或者jarsinger.jar兩個工具。將根據(jù)我們提供的Debug和Release兩個版本的Keystore進行相應的簽名。
第8步:zipalign優(yōu)化
Zipalign是一個Android平臺上整理APK文件的工具,它首次被引入是在Android 1.6版本的SDK軟件開發(fā)工具包中。它能夠對打包的Android應用程序進行優(yōu)化, 以使Android操作系統(tǒng)與應用程序之間的交互作用更有效率,這能夠讓應用程序和整個系統(tǒng)運行得更快。用Zipalign處理過的應用程序執(zhí)行時間達到最低限度,當設備運行APK應用程序時占更少的RAM。
- Zipalign如何進行優(yōu)化的呢?
調(diào)用buildtoolszipalign,對簽名后的APK文件進行對齊處理,使APK中所有資源文件距離文件起始偏移為4字節(jié)的整數(shù)倍,從而在通過內(nèi)存映射訪問APK文件時會更快。同時也減少了在設備上運行時的內(nèi)存消耗。如果對于為何提速不理解,那么可以看下內(nèi)存對齊的規(guī)則以及作用該篇文章,對于內(nèi)存對齊的好處有比較生動詳細的解釋。最終這樣我們的APK就生成完畢了。
典型的APK中內(nèi)容
AndroidManifest.xml
程序全局配置文件classes.dex
Dalvik字節(jié)碼resources.arsc
資源索引表META-INF
該目錄下存放的是簽名信息res
該目錄存放資源文件assets
該目錄可以存放一些配置或資源文件
總結
至此,對于Andoid項目構建過程的分析已經(jīng)完成,當然,并沒與深入到源碼層級的分析,本文的旨在對于構建過程流程上的了解和其中一些優(yōu)化的原因所在,為后續(xù)通過Gradle插件hook構建過程來做一定的操作,做一個鋪墊。接下來會推出對于構建AndroidStudio上運行的Gradle插件的一系列文章。
參考文章:https://segmentfault.com/a/1190000008445988
http://www.lxweimin.com/p/4962634901fb