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