Android插件化基礎(chǔ)的主要內(nèi)容包括
- Android插件化基礎(chǔ)1-----加載SD上APK
- Android插件化基礎(chǔ)2----理解Context
- Android插件化基礎(chǔ)3----Android的編譯打包APK流程詳解
- Android插件化基礎(chǔ)4----APK安裝流程詳解(APK安裝流程詳解0——前言)
- Android插件化基礎(chǔ)5----Rsources.arsc詳解(請(qǐng)期待)
- Android插件化基礎(chǔ)6----Android的資源系統(tǒng)(請(qǐng)期待)
- Android插件化基礎(chǔ)7----Activity的啟動(dòng)流程(請(qǐng)期待)
- Android插件化基礎(chǔ)8----如何啟動(dòng)一個(gè)沒有注冊(cè)過的Activity(請(qǐng)期待)
- Android插件化基礎(chǔ)9----Service的啟動(dòng)流程(請(qǐng)期待)
- Android插件化基礎(chǔ)10----BroadcastReceiver源碼解析(請(qǐng)期待)
本片文章的主要內(nèi)容如下:
- 1、關(guān)于APK
- 2、官網(wǎng)流程簡(jiǎn)述
- 3、相關(guān)工具介紹
- 4、打包流程詳解
- 5、關(guān)于Android自動(dòng)打包工具aapt的概述
- 6、面試中關(guān)于APK打包的問題
- 7、混淆
一、關(guān)于APK
.apk文件其實(shí)就是一個(gè)壓縮包,把文件的后綴改成.zip,用壓縮軟件解壓搜就可的下圖(我是mac)
我們就把上面的內(nèi)容簡(jiǎn)單介紹下:
- AndroidManifest.xmlAndroidManifest.xml:
是每個(gè)android程序必須的文件,它位于整個(gè)項(xiàng)目的根目錄,描述了package中暴露的組件(activity,service等),他們的實(shí)現(xiàn)類、處理數(shù)據(jù)、啟動(dòng)模式等。除了可以聲明程序中的Activity、Service、ContentProvider、Receive,還能指定permissions(權(quán)限控制)和instrumentation(測(cè)試)。這個(gè)文件很重要,里面有我們的四大組件和申請(qǐng)的權(quán)限- classes.dex:
它是Android平臺(tái)上的可執(zhí)行文件,Android虛擬機(jī)Dalvik支持的字節(jié)碼文件格式Google在Android平臺(tái)上使用自己的Dalvik虛擬機(jī)來定義,這種虛擬機(jī)執(zhí)行的并非Java字節(jié)碼,而是另一種字節(jié)碼:dex格式的字節(jié)碼。在編譯Java代碼之后,通過Android平臺(tái)上的工具可以將Java字節(jié)碼轉(zhuǎn)換成Dex字節(jié)碼。雖然Google稱Dalvik是為了移動(dòng)設(shè)備定做的,但是業(yè)界很多人認(rèn)為這是為了規(guī)避向sun(現(xiàn)在是oracle)申請(qǐng)Javalicense。這個(gè)DalvikVM針對(duì)手機(jī)程序/CPU做過最佳化,可以同樣執(zhí)行許多VM而不會(huì)占用太多的資源。class.dex也是由Java的class文件重新編排而來,我們也可以通過反編譯工具把dex文件轉(zhuǎn)換成class文件。如果做了拆包那么會(huì)有classes1.dex,classes2.dex ...多個(gè)classes.dex文件。- META-INF:
簽名文件夾:里面存放三個(gè)文件,有兩個(gè)是對(duì)的資源文件做的SHA1 hash處理,一個(gè)是簽名和公鑰證書。- res:
資源文件夾,和咱們開發(fā)中使用的res是同一個(gè)東西- resources.arsc:
這個(gè)文件記錄了所有應(yīng)用程序資源目錄的信息,包括每一個(gè)資源名稱、類型、值、ID以及所配置的維度信息。我們可以將這個(gè)resources.arsc可以理解為資源索引表,這個(gè)資源索引表在給定資源ID和設(shè)備配置信息的情況下,能夠在應(yīng)用程序目錄中快速找到最匹配的資源。- lib:
lib文件夾里面存放的是so動(dòng)態(tài)鏈接庫,so動(dòng)態(tài)鏈接庫是不需要做處理apk打包一些壓縮處理的。
二、官方流程簡(jiǎn)述
現(xiàn)在官網(wǎng)支持中文哦,強(qiáng)烈大家先去把官網(wǎng)讀一遍
Android Developer官網(wǎng),點(diǎn)擊查看
如下圖
下圖的是官網(wǎng)對(duì)于Android編譯打包流程的介紹:
虛線方框是打包APK的操作,現(xiàn)在開發(fā)Android都是使用的Android Studio基于gradle來構(gòu)建項(xiàng)目,所有打包操作都是執(zhí)行g(shù)radle腳本來完成,gradle編譯腳本具有強(qiáng)大的功能,我們可以在里面完成多渠道,多版本,不同版本使用不同代碼,不同的資源,編譯后的文件重命名,混淆簽名驗(yàn)證等等配置,雖然都是基于AndroidSdk的platform-tools的文件夾下面的工工具來完成的,但是有了gradle這個(gè)配置文件,這樣就便捷了。
三、相關(guān)工具介紹
PS:這里補(bǔ)充下apkbuilder在SDK3.0之前使用apkbuilder去打包,在SDK3.0之后就棄用了,而使用sdklib.jar打包apk。
如果你有下載Android系統(tǒng)源碼,會(huì)發(fā)現(xiàn)源碼目錄下搜索apkbuilder,在sdk中有apkbuilder文件夾,里面有個(gè)readme文檔,說明如下:
The apkbuilder command linetool is deprecated, and is not maintained anymore.
It is lacking recent buildimprovements such as support for Library Projects.
Its source code has been movedinto sdklib.
It is recommended to directlyuse the com.android.sdklib.build.ApkBuilder class instead.
就是說明此命令行工具新版本SDK目錄去掉了,不再支持使用,它的代碼放進(jìn)了sdklib.jar可以直接使用。
其實(shí),以前的apkbuilder.bat內(nèi)部也是執(zhí)行
com.android.sdklib.build.ApkBuilderMain
這個(gè)類其實(shí)就是在sdklib里面。
四、打包流程詳解
提供一張APK打包流程圖如下:
整體概述如下:
- 1 打包資源文件,生成R.java文件
- 2 處理aidl文件,生成相應(yīng)的.java文件
- 3 編譯工程源碼,生成相應(yīng)的class文件
- 4 轉(zhuǎn)換所有的class文件,生成classes.dex文件
- 5 打包生成apk
- 6 對(duì)apk文件進(jìn)行簽名
- 7 對(duì)簽名后的apk進(jìn)行對(duì)齊處理
下面我們就詳細(xì)看下
(一)、打包資源文件,生成R.java文件
1、輸入
- 項(xiàng)目工程中res中的文件夾,我們稱之為Resource文件
- 項(xiàng)目工程中assert的文件夾,我們稱之為Assert文件
- AndroidManifest.xml文件
- Android基礎(chǔ)庫(Android.jar文件)
2、工具:
aapt
3、過程:
生成過程主要是調(diào)用了aapt源碼目錄下的Resouce.cpp文件的buildResources()函數(shù),該函數(shù)首先檢查AndroidManifest.xml的合法性,然后對(duì)res目錄下的資源目錄進(jìn)行處理,處理函數(shù)為makeFileResource(),處理的內(nèi)容包括資源文件名的合法性檢查,向資源表table添加條目等,處理完后調(diào)用compileResourceFile()函數(shù)編譯res與asserts目錄下的資源并生成resource.arsc文件,compileResourceFile()函數(shù)位于appt源碼目錄的ResourceTable.cpp文件中,該函數(shù)最后會(huì)調(diào)用parseAndAddEntry()函數(shù)生成R.java文件,完成資源編譯后,接下來調(diào)用compileXmlfile()函數(shù)對(duì)res目錄的子目錄下的xml文件進(jìn)行編譯,這樣處理過的xml文件就簡(jiǎn)單的被"加密"了,最后將所有資源與編譯生成的resource.arsc文件以及"加密"過的AndroidManifest.xml打包壓縮成resources.ap_文件。
上面涉及的源碼代碼位置在:
- buildResources()函數(shù)在:Resouce.cpp 1144行
- makeFileResource()函數(shù)在:Resouce.cpp 297行
- compileResourceFile()函數(shù)在:ResourceTable.cpp 782行
- parseAndAddEntry()函數(shù)在:ResourceTable.cpp 690行
- compileXmlfile()函數(shù)在:ResourceTable.cpp 42/57/73行
4、輸出:
打包好的資源包括:
- resources.ap_文件
- R.java文件
5、補(bǔ)充:
打包資源的工具aapt,大部分文本格式的XML資源文件會(huì)被編譯成二進(jìn)制格式的XML資源文件,除了assets和res/raw資源被原封不動(dòng)地打包進(jìn)APK之外,其他資源都會(huì)被編譯或者處理。
PS:
- 除了assets和res/raw資源被原封不動(dòng)地打包進(jìn)APK之外,其它的資源都會(huì)被編譯或者處理,除了assets資源之外,其他的資源都會(huì)被賦予一個(gè)資源ID。
- resources.arsc是清單文件,但是resources.arsc跟R.java區(qū)別還是非常大的,R.java里面的只是id列表,并且里面的id值不重復(fù)。但是我們我們知道drawable-xdpi或者drawable-xxdpi這些不同分辨率的文件夾存放的圖片和名稱和id是一樣的,在運(yùn)行的時(shí)候是怎么根據(jù)設(shè)備的分別率來選擇對(duì)應(yīng)的分辨率的圖片?這時(shí)候就需要resources.arsc這個(gè)文件了,resources.arsc里面會(huì)對(duì)所有的資源id進(jìn)行組裝,在apk運(yùn)行是會(huì)根據(jù)設(shè)備的情況來采用不同的資源。resource.arsc文件的作用就是通過一樣的ID,根據(jù)不同的配置索引到最佳的資源現(xiàn)在UI中。
- R.java 是我們?cè)趯懘a時(shí)候引用的res資源的id表,resources.arsc是程序在運(yùn)行時(shí)候用到的資源表。R.java是給程序員讀的,resources.arsc是給機(jī)器讀的。
大體情況如下:
(二)、處理aidl文件,生成相應(yīng)的.java文件
1、輸入:
源碼文件、aidl文件、framework.aidl文件
2、工具:
AIDL工具
3、過程:
4、輸出:
對(duì)應(yīng)的.java文件
5、補(bǔ)充:
對(duì)于沒有使用到的aidl的android工程,這一步可以跳過,aidl工具解析接口定義文件并生成相應(yīng)的.java文件,供程序調(diào)用
(三)、編譯工程源碼,生成相應(yīng)的class文件
1、輸入:
源碼文件包括
- R.java
- AIDL生成的.java文件
- 庫jar文件
2、工具:
javac 工具
3、過程:
這里調(diào)用了javac編譯工程的src目錄下所有的java源文件,生成的class文件位于工程的bin\classess目錄下,上面假定編譯源代碼時(shí)程序是基于android SDK 開發(fā)的,實(shí)際開發(fā)過程中,也有可能會(huì)使用android NDK來編譯native代碼,因此,如果可能的話,這一步還需要使用android NDK編譯C/C++代碼,當(dāng)然,編譯C/C++代碼的步驟也可以提前到第一步或第二步。
4、輸出:
.class文件
(四)、轉(zhuǎn)換所有的class文件,生成classes.dex文件
1、輸入:
.class文件,主要包括AIDL生成的.class文件,R生成的.class文件,源文件生成的.class文件、.jar庫文件
2、工具:
dx
3、過程:
前面提到,Android系統(tǒng)的dalvik虛擬機(jī)的可執(zhí)行文件為dex格式,程序運(yùn)行所需的classes.dex文件就是在這一步生成的,使用的工具為dx,dx工具主要的工作是將java字節(jié)碼轉(zhuǎn)換為dalvik字節(jié)碼、壓縮常量池、消除冗余信息等。
4、輸出:
.dex文件
5、補(bǔ)充:
5.1 class與dex
- class基于棧
- dex基于寄存器
5.2 優(yōu)化
- 1、優(yōu)化常量池
- 2、基于寄存器跟容易操作硬件內(nèi)容,適合移動(dòng)端
(五)、打包生成apk
1、輸入:
- 打包后的資源文件
- 打包后的類文件,主要是指.dex文件
- libs文件,包括so文件
2、工具:
3.0之前用apkbuilder工具,但是apkbuilder內(nèi)部也是引用sdklib的ApkBuilderMain,所以3.0之后直接使用了sdklib的ApkBuilderMain
3、過程:
打包工具為apkbuilder,apkbuilder為一個(gè)腳本文件,實(shí)際調(diào)用的是android-sdk/tools/lib/sdklib.jar文件中的com.android.sdklib.build.ApkBuilderMain類。它的代碼實(shí)現(xiàn)位于android系統(tǒng)源碼的sdk/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilderMain.java文件,代碼構(gòu)建了一個(gè)ApkBuilder類,然后以包含resources.arsc文件為基礎(chǔ)生成一個(gè)apk文件,這個(gè)文件一般為ap_結(jié)尾,接著調(diào)用addSourceFolder()函數(shù)添加工程資源,addSourceFolder()會(huì)調(diào)用processFileForResource()函數(shù)往apk文件中添加資源,處理內(nèi)容包括res目錄和asserts目錄中的文件,添加完資源后調(diào)用addResourceFromJar()函數(shù)往apk文件中寫入依賴庫,接著調(diào)用
addNativeLibraries()函數(shù)添加工程libs目錄下的Nativie庫,最后調(diào)用sealApk(),關(guān)閉apk文件。
4、輸出:
未簽名的.apk文件
(六)、對(duì)apk文件進(jìn)行簽名
1、輸入:
未簽名的.apk文件
2、工具:
jarsigner
3、過程:
android的應(yīng)用程序需要簽名才能在android設(shè)備上安裝,簽名apk文件有兩種情況:
- 在調(diào)用應(yīng)用程序時(shí),也就是我們通常稱為的debug模式的簽名,平時(shí)開發(fā)的時(shí)候,在編譯調(diào)試程序時(shí)會(huì)自己使用一個(gè)debug.keystore對(duì)apk進(jìn)行簽名
- 正式發(fā)布時(shí)對(duì)應(yīng)用程序打包進(jìn)行簽名,這種情況下需要提供一個(gè)符合android開發(fā)文檔中要求的簽名文件。
簽名也是分兩種:- 1 是使用JDK中提供的jarsigner工具簽名
- 2 是使用android源碼中提供的signapk工具,它的代碼位于android系統(tǒng)源碼build/tools/signapk目錄下
4、輸出:
簽名的apk文件
(七)、對(duì)簽名后的apk進(jìn)行對(duì)齊處理
對(duì)齊的作用就是減少運(yùn)行內(nèi)存的使用。
1、輸入:
簽名后的.apk文件
2、工具:
zipalign工具
3、過程:
這一步需要使用的工具為zipalign,它位于android-sdk/tools目錄,源碼位于android系統(tǒng)資源的build/tools/zipalign目錄,它的主要工作是將apk包進(jìn)行對(duì)齊處理,使apk包中的所有資源文件舉例文件起始偏移為4字節(jié)的整數(shù)倍,這樣通過內(nèi)存映射訪問apk時(shí)的速度會(huì)更快,驗(yàn)證apk文件是否對(duì)齊過的工作由ZipAlign.cpp文件的verify()函數(shù)完成,處理對(duì)齊的工作則由process()函數(shù)完成。
4、輸出:
對(duì)齊后的apk文件
整體的細(xì)節(jié)流程如下圖:
五、關(guān)于Android自動(dòng)打包工具aapt概述
(一) 概述
在Android.mk中有LOCAL_AAPT_FLAGS配置項(xiàng),在gradle中也有aaptOptions,那么aapt到底是干什么?
aapt即Android Asset Packaging Tool (Android 打包工具),在SDK的build-tools目錄下。大家可以自行查看。它可以將資源文件編譯成二級(jí)制文件,盡管你可能沒有直接使用過aapt工具,但是build scripts 和IDE插件會(huì)使用這個(gè)工具打包APK文件構(gòu)成Android應(yīng)用程序
(二)aapt打包流程
aapt傳統(tǒng)的打包主要指的是res和Java代碼的打包,aapt打包走的是單線程,流水式的任務(wù)從上到下進(jìn)行打包構(gòu)建。傳統(tǒng)的aapt打包,aapt會(huì)執(zhí)行2次,第一次是生成R.java,參與javac編譯,第二次是對(duì)res里面的資源文件進(jìn)行編譯,最后將Dex文件與編譯好的資源文件打包成apk,進(jìn)行簽名。整個(gè)流程下來沒有任務(wù)緩存,沒有并發(fā),也沒有增量,每次構(gòu)建都是一個(gè)全新的流程。所以每次構(gòu)建時(shí)間也比較恒定,代碼量,資源量越多,構(gòu)建的時(shí)間越慢。
如下圖
六、面試中關(guān)于APK打包的問題
1、為什么第一步需要用aapt把xml文件編譯成二進(jìn)制文件?
主要是因?yàn)閮蓚€(gè)原因:
- 首先二進(jìn)制格式的XML文件占用空間更小。因?yàn)樗械腦ML元素的標(biāo)簽、屬性名稱、屬性值和內(nèi)容所涉及到的字符串都會(huì)被統(tǒng)一收集到一個(gè)字符串資源池中去,并且會(huì)去重。有了這個(gè)字符串資源池,原來使用字符串的地方就會(huì)被替換成一個(gè)索引字符串資源池的整數(shù)值,從而可以減少文件的大小
- 其次是二進(jìn)制的XML文件解析速度更快,這是由于二進(jìn)制的XML元素里面不再包含有字符串值,因此可以避免了進(jìn)行字符串解析,從而提高速度。
2、Dex打包關(guān)于65536的問題
這個(gè)問題是由于DEX文件格式限制,一個(gè)DEX文件中的method個(gè)數(shù)采用使用原生類型short來索引文件的方法,也就是4個(gè)字節(jié)共計(jì)最多表達(dá)65536個(gè)method,field/class個(gè)數(shù)也均有此限制,對(duì)于DEX文件,則是將工程所需要全部class文件合并壓縮到一個(gè)DEX文件期間,也就是Android打包的DEX過程中,單個(gè)DEX文件可被引用的方法總數(shù)(自己開發(fā)的代碼以及所引用的Android框架、類庫的代碼)被限制為66536。
3、打包流程中最后一步,為什么要對(duì)齊?
對(duì)齊是為了加快資源的訪問速度。如果每個(gè)資源的開始位置上都是一個(gè)資源之后的4n字節(jié),那么訪問下一個(gè)資源就不用遍歷,直接跳到4字節(jié),那么訪問下一個(gè)資源就不用遍歷,直接跳到4*n字節(jié)處判斷是不是一個(gè)新的資源即可。有點(diǎn)類似于資源數(shù)組化,數(shù)組的訪問速度當(dāng)然比鏈表塊
4、Android是怎么通過R文件找到真正的資源文件?
aapt工具對(duì)每個(gè)資源文件都生成了唯一的ID,這些ID保存在R.java文件中。資源ID是一個(gè)4字節(jié)的的無符號(hào)證書,在R.java文件中用16進(jìn)程表示。其中,最高的1字節(jié)表示Package ID,次高1個(gè)字節(jié)表示Type ID,最低2字節(jié)表示Entry ID。只有一個(gè)ID 如何能引用到實(shí)際資源?實(shí)際上aapt工具還生成一個(gè)文件resources.arsc,相當(dāng)于一個(gè)資源索引表,或者你理解成一個(gè)map也行,map的key是資源ID,value是資源在apk文件中的路徑。resource.arsc里面還有其他信息,這里就不多說了。通過resource.arsc配合,就能引用到實(shí)際的資源文件。
七、混淆
說到打包就不能不提一下混淆,說到混淆就不能不提ProGuard。
(一)、ProGurad簡(jiǎn)介
- 因?yàn)镴ava代碼是非常容易反編碼的,況且Android開發(fā)的應(yīng)用程序是用Java代碼寫的,為了很好的保護(hù)Java源代碼,我們需要對(duì)編譯好的后的class文件進(jìn)行混淆。
ProGuard是一個(gè)混淆代碼的開源項(xiàng)目,它的主要作用是混淆代碼,但是其實(shí)它主要有4個(gè)功能如下:
- 1 壓縮(Shrink):檢測(cè)并移除代碼中無用的類、字段、方法和特性(Attribute)
- 2 優(yōu)化(Optimize):字節(jié)碼進(jìn)行優(yōu)化,移除無用的指令。
- 3 混淆(Obfuscate):使用a、b、c、d這樣簡(jiǎn)短而無意義的名稱,對(duì)壘、字段和方法進(jìn)行重命名。
- 4 預(yù)檢測(cè)(Preveirfy):在Java平臺(tái)對(duì)處理后的代碼進(jìn)行預(yù)檢測(cè),確保加載class文件是可執(zhí)行的。
ProGuard的官網(wǎng),根據(jù)官網(wǎng)的翻譯:
Progurad是一個(gè)Java類文件的壓縮器、優(yōu)化器、混淆器、預(yù)檢測(cè)器。壓縮環(huán)節(jié)會(huì)檢測(cè)以及移除沒有用到的類、字段、方法以及屬性。優(yōu)化環(huán)節(jié)會(huì)分析以及優(yōu)化方法的字節(jié)碼。混淆環(huán)節(jié)會(huì)用無意義的端變量去重命名類、變量、方法。這些步驟讓代碼更加精簡(jiǎn)、更搞笑,也更難被逆向破解。
PS:
- 1、 如果僅僅是為了代碼混淆,ProGuard有一個(gè)兄弟產(chǎn)品DexGuard,有興趣的可以去試試,地址在http://www.saikoa.com/dexguard
- 2 、ProGurad是一個(gè)開源項(xiàng)目在SourceForge上進(jìn)行維護(hù),地址在http://ProGuard.sourceforge.net。從上述地址下載ProGuard之后,能同時(shí)看到官方文檔和示例,不過是英文的,目前市面上沒有相應(yīng)的中文翻譯版,也沒有一片詳盡的介紹文章。
(二)、ProGurad的使用
現(xiàn)在大多數(shù)開發(fā)者都是用了Android Studio,只有很少的一部分才使用Eclipse,所以我兩部分都說下
1、Android Studio中如何開啟混淆
在build.gradle中修改minifyEnable修改為true即可
代碼如下
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
2、Eclipse中如何開啟混淆
在Eclipse中,文件根目錄有如下兩個(gè)文件 projiect.properties 和 proguard-project.txt。開啟混淆打包只需要在 projiect.properties 中,被注釋的有如下一句話
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
把他注釋去掉即可
在開啟混淆中,Android Studio和eclipse中都有一個(gè)文件proguard-android.txt,這是混淆的一個(gè)默認(rèn)文件,該默認(rèn)文件為android提供的一個(gè)默認(rèn)規(guī)則,如果我們的工程沒有引入第三方庫等,那么很簡(jiǎn)單的就能混淆了。
(三)、ProGuard工作原理
ProGuard 由shrink、optimize、obfuscate和preverify四個(gè)步驟組成,每個(gè)步驟都是可選的,需要那些步驟都可以在腳本中配置。
ProGuard流程如下:
Input jars、Library jars——>shrink ——>Shrunk code——>optimize ——>Optim.code——>obfuscate——>Obfusc.code
——>preverify——>Output jars、Library jars
ProGuard使用Library jars來輔助對(duì)input jars類之間的依賴關(guān)系進(jìn)行解析,Library jars本身不會(huì)被處理,也不會(huì)被包含到output jars中。
混淆中會(huì)移除沒有用到的代碼,所以這里就產(chǎn)生一個(gè)疑問,ProGuard怎么知道這個(gè)代碼沒有被用到?
這里引入到一個(gè)Entry Point(入口點(diǎn)) 概念,Entry Point是在ProGurad過程中不會(huì)被處理的類或方法。在壓縮的過程中,ProGuard會(huì)從上述的Entry Point開始遞歸遍歷,搜索哪些類和類的成員在使用,對(duì)于沒有使用的類和類的成員,就會(huì)在壓縮端被丟棄,在接下來的優(yōu)化過程中,那些非Entry Point類、方法都會(huì)被設(shè)置為private、static或final,不實(shí)用的參數(shù)會(huì)被移除,此外,有些方法會(huì)被標(biāo)記為內(nèi)聯(lián)的,在混淆的不會(huì)走中,ProGuard會(huì)對(duì)非Entry Point的類和方法進(jìn)行重命名。
(四)、ProGuard的工具目錄
- bin目錄:
bin目錄中包含了幾個(gè)bat和shell腳本,通過這些腳本可以直接執(zhí)行proguard.jar,proguardgui.jar和retrace.jar。如果將bin目錄添加到環(huán)境變量中,就可以直接在命令行中執(zhí)行progurad,proguardgui和retrace命令了,避免每次都要輸入 java -jar +- lib目錄:
lib目錄包含了Proguard工具對(duì)應(yīng)的jar文件,其中又包含三個(gè)文件:proguard.jar,proguardgui.jar和retrace.jar。
- proguard.jar:Progurad的四項(xiàng)核心功能shrink.optimize,obfuscate和preverify的執(zhí)行都是由progurad.jar來完成,不過proguard.jar只能通過領(lǐng)命行方式來使用。
- proguardgui.jar:是Proguard提供的一個(gè)圖形界面工具,通過proguardgui.jar可以方便的查看和編輯Proguard配置,以及調(diào)用proguard.jar來執(zhí)行一次優(yōu)化過程。
- retrace.jar主要是在debug時(shí)使用。混淆之后的jar文件執(zhí)行過程如果出現(xiàn)異常,生成的異常信息將很難被解讀,方法調(diào)用的堆棧都是一些混淆之后的名字,通過retrace.jar可以將異常的堆棧信息中的方法名還原成混淆前的名字,方便程序解決bug。
(五)、ProGuard的基本命令
1、關(guān)鍵字:
- keep關(guān)鍵字
- keep:保留類和類中的成員,防止他們被混淆
- keepnames:保留類和類中的成員防止被混淆,但成員如果沒有被引用將被刪除
- keepclassmember:只保留類中的成員,防止被混淆和移除
- keepclassmembernames:值保留類中的成員,但是如果成員沒有被引用將被刪除
- keepclasseswithmember:如果當(dāng)前類中包含指定的方法,則保留類和類成員,否則將被混淆。
- keepclasseswithmembernames:如果當(dāng)前類中包含指定的方法,則保留類和類成員,如果類成員沒有被引用則會(huì)被移除。
- keepattributes Signature:避免混淆泛型
- keepattributes SourceFile,LineNumberTable:拋出異常時(shí)保留行號(hào)。
- dontwarn:忽視警告
- optimizationpasses 5:代碼混淆的壓縮比,0~7之間,默認(rèn)是5,一般不做修改
2、編寫ProGuard文件
編寫ProGuard文件有3個(gè)步驟:
- 第一步,基本混淆
- 第二步,針對(duì)APP量身定制
- 第三步,針對(duì)第三方j(luò)ar包的解決方法
下面我們就來詳細(xì)看下
2.1 基本混淆
基本混淆又可以分為
- 基本指令
- 需要保留的東西
2.1.1 基本混淆
混淆文件的基本配置信息,任何APP都要使用,可以作為模板使用,具體如下:
# 代碼混淆壓縮比,在0和7之間,默認(rèn)為5,一般不需要改
-optimizationpasses 5
# 混淆時(shí)不使用大小寫混合,混淆后的類名為小寫
-dontusemixedcaseclassnames
# 指定不去忽略非公共的庫的類
-dontskipnonpubliclibraryclasses
# 指定不去忽略非公共的庫的類的成員
-dontskipnonpubliclibraryclassmembers
# 不做預(yù)校驗(yàn),preverify是proguard的4個(gè)步驟之一
# Android不需要preverify,去掉這一步可加快混淆速度
-dontpreverify
# 有了verbose這句話,混淆后就會(huì)生成映射文件
# 包含有類名->混淆后類名的映射關(guān)系
# 然后使用printmapping指定映射文件的名稱
-verbose
-printmapping proguardMapping.txt
# 指定混淆時(shí)采用的算法,后面的參數(shù)是一個(gè)過濾器
# 這個(gè)過濾器是谷歌推薦的算法,一般不改變
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保護(hù)代碼中的Annotation不被混淆,這在JSON實(shí)體映射時(shí)非常重要,比如fastJson
-keepattributes *Annotation*
# 避免混淆泛型,這在JSON實(shí)體映射時(shí)非常重要,比如fastJson
-keepattributes Signature
//拋出異常時(shí)保留代碼行號(hào),在異常分析中可以方便定位
-keepattributes SourceFile,LineNumberTable
-dontskipnonpubliclibraryclasses用于告訴ProGuard,不要跳過對(duì)非公開類的處理。默認(rèn)情況下是跳過的,因?yàn)槌绦蛑胁粫?huì)引用它們,有些情況下人們編寫的代碼與類庫中的類在同一個(gè)包下,并且對(duì)包中內(nèi)容加以引用,此時(shí)需要加入此條聲明。
-dontusemixedcaseclassnames,這個(gè)是給Microsoft Windows用戶的,因?yàn)镻roGuard假定使用的操作系統(tǒng)是能區(qū)分兩個(gè)只是大小寫不同的文件名,但是Microsoft Windows不是這樣的操作系統(tǒng),所以必須為ProGuard指定-dontusemixedcaseclassnames選項(xiàng)
2.1.2 需要保留的東西
# 保留所有的本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留了繼承自Activity、Application這些類的子類
# 因?yàn)檫@些子類有可能被外部調(diào)用
# 比如第一行就保證了所有Activity的子類不要被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
# 如果有引用android-support-v4.jar包,可以添加下面這行
-keep public class com.null.test.ui.fragment.** {*;}
# 保留Activity中的方法參數(shù)是view的方法,
# 從而我們?cè)趌ayout里面編寫onClick就不會(huì)影響
-keepclassmembers class * extends android.app.Activity {
public void * (android.view.View);
}
# 枚舉類不能被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留自定義控件(繼承自View)不能被混淆
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(***);
*** get* ();
}
# 保留Parcelable序列化的類不能被混淆
-keep class * implements android.os.Parcelable{
public static final android.os.Parcelable$Creator *;
}
# 保留Serializable 序列化的類不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 對(duì)R文件下的所有類及其方法,都不能被混淆
-keepclassmembers class **.R$* {
*;
}
# 對(duì)于帶有回調(diào)函數(shù)onXXEvent的,不能混淆
-keepclassmembers class * {
void *(**On*Event);
}
2.2 針對(duì)APP量身定制
針對(duì)APP量身定制里面又包含
- 1、保留實(shí)體類和成員被混淆
- 2、內(nèi)部類
- 3、對(duì)WebView的處理
- 4、對(duì)JavaScript的處理
- 5、處理反射
- 6、對(duì)于自定義View的解決方案
下面我們就來一一介紹
2.2.1 保留實(shí)體和成員比混淆
對(duì)于實(shí)體,保留他們的set和get方法,對(duì)于boolean型get方法,有人喜歡命名isXXX,所以不要遺漏。如下:
# 保留實(shí)體類和成員不被混淆
-keep public class com.xxxx.entity.** {
public void set*(***);
public *** get*();
public *** is*();
}
一種好的做法就是把所有實(shí)體都放到一個(gè)包下進(jìn)行管理,遮掩只寫一次混淆就夠了,避免以后在別的包中新增的實(shí)體而忘記保留,代碼在混淆后因?yàn)檎也坏较鄳?yīng)的實(shí)體類而崩潰。
2.2.2 內(nèi)部類
內(nèi)部類經(jīng)常會(huì)被混淆,結(jié)果在調(diào)用的時(shí)候?yàn)榭站捅罎⒘耍詈玫慕鉀Q辦法就是把這個(gè)內(nèi)部類拿出來,單獨(dú)成為一個(gè)類。如果一定要內(nèi)置,那么這個(gè)類就必須在混淆的時(shí)候保留,比如:
# 保留內(nèi)嵌類不被混淆
-keep class com.example.xxx.MainActivity$* { *; }
這個(gè)$符號(hào)就是用來分割內(nèi)部類與其母體的標(biāo)志。
2.2.3 對(duì)WebView的處理
# 對(duì)WebView的處理
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String);
}
2.2.4 對(duì)JavaScript的處理
# 保留JS方法不被混淆
-keepclassmembers class com.example.xxx.MainActivity$JSInterface1 {
<methods>;
}
其中JSInterface是MainActivity的子類
2.2.5 處理反射
在程序中使用SomeClass.class.method這樣靜態(tài)方法,在ProGuard中是在壓縮過程中被保留的,那么對(duì)于Class.forName("SomeClass")呢,SomeClass不會(huì)被壓縮過程中移除,它會(huì)檢查程序中使用的Class.forName方法,對(duì)參數(shù)SomeClass法外開恩,不會(huì)被移除。但是在混淆過程中,無論是Class.forName("SomeClass"),還是SomeClass.class,都不能蒙混過關(guān),SomeClass這個(gè)類名稱會(huì)被混淆,因此,我們要在ProGuard.cfg文件中保留這個(gè)名稱。
Class.forName("SomeClass")
SomeClass.class
SomeClass.class.getField("someField")
SomeClass.class.getDeclaredField("someField")
SomeClass.class.getMethod("someMethod", new Class[] {})
SomeClass.class.getMethod("someMethod", new Class[] { A.class })
SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")
在混淆的時(shí)候,要在項(xiàng)目中搜索一下上述方法,將相應(yīng)的類或者方法名稱進(jìn)行保留而不被混淆。
2.2.6 對(duì)于自定義View的解決方案
但凡在Layout目錄下XML布局文件配置的自定義View,都不能進(jìn)行混淆。為此要遍歷Layout下所有XML布局文件,找到那些自定義的View,然后確認(rèn)其是否在ProGuard文件中保留。有一種思路是,在我們使用自定義View時(shí),前面都必須加上我們的包名,比如com.a.b.customview,我們可以遍歷所有Layout下的XML布局文件,查找所有匹配的com.a.b標(biāo)簽即可
但凡在Layout目錄下的XML布局文件配置的自定義View,都不能進(jìn)行混淆。為此要遍歷Layout下的所有的XML布局文件,找到那些自定義View,然后確認(rèn)其是否在ProGuard文件中保留。有一種思路是,在我們使用自定義View時(shí),前面都必須加上我們的包名,比如com.a.b.customeview,我們可以遍歷所有Layout下的XML布局文件,查找所有匹配com.a.b的標(biāo)簽即可
2.3 針對(duì)第三方j(luò)ar包的解決方案
我們?cè)贏ndroid項(xiàng)目中不可避免要使用很多第三方提供的SDK,一般而言,這些SDK是經(jīng)過ProGuard混淆的,而我們所需要做的就是避免這些SDK的類和方法在我們APP被混淆。
2.3.1 針對(duì)android-support-v4.jar的解決方案
# 針對(duì)android-support-v4.jar的解決方案
-libraryjars libs/android-support-v4.jar
-dontwarn android.support.v4.**
-keep class android.support.v4.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.app.Fragment
2.3.2 其他的第三方j(luò)ar包的解決方案
這個(gè)就取決于第三方包的混淆策略,一般都有在各自的SDK中有關(guān)于混淆的說明文字,比如支付寶如下:
# 對(duì)alipay的混淆處理
-libraryjars libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.** { *; }
PS:
值得注意的是,不是每個(gè)第三方SDK都需要-dontwarn指令、這取決于混淆時(shí)第三方SDK是否出現(xiàn)警告,需要的時(shí)候再機(jī)上。
(六)、ProGuard的混淆的注意事項(xiàng)
在使用ProGuard過程中,還有一些注意事項(xiàng)如下:
- 1、如何確保混淆不會(huì)對(duì)項(xiàng)目產(chǎn)生影響
測(cè)試工作要基于混淆進(jìn)行,才能盡早發(fā)現(xiàn)問題,開發(fā)團(tuán)隊(duì)的冒煙測(cè)試,也是要基于混淆包,發(fā)版前,重點(diǎn)的功能和模塊要額外的測(cè)試,包括推送,分享等- 2、打包時(shí)忽略警告
當(dāng)打包的時(shí)候,會(huì)發(fā)現(xiàn)很多could not reference class之類的warning信息,如果確認(rèn)App在運(yùn)行中和那些以后能用沒有什么關(guān)系,可以添加-dontwarn 標(biāo)簽,就不會(huì)提示這些警告信息了。- 3、對(duì)于自定義類庫的混淆處理
比如我們引用了一個(gè)叫做AndroidLib的類庫,我們需要對(duì)Lib也進(jìn)行混淆,然后在主項(xiàng)目的混淆文件中保留AndroidLib中的類和類成員- 4、使用annotation避免混淆
另一種類或者屬性被混淆的方式時(shí),使用annotation,如下:@keep @keepPublicGetterSetters public class Bean{ public boolean booleanProperty; public int intProperty; public String stringProperty; }
答:xml里面都是各種字符,不利于快速遍歷。編譯成二進(jìn)制文件,用數(shù)字替換各種符號(hào),一方面能快速訪問,另一方面也能減少大小。