本文主要討論對(duì)apk文件的壓縮和混淆中的細(xì)節(jié)問(wèn)題以及分析在壓縮混淆過(guò)程中遇到的問(wèn)題的原因.
ProGuard壓縮混淆Java代碼
ProGuard的用法和自定義規(guī)則可以參考Android Proguard(混淆), 本文不作陳列說(shuō)明.
壓縮的意思是刪除沒(méi)有被直接使用的類(lèi)和類(lèi)成員(包括fields和methods).
混淆的意思是將類(lèi)或者類(lèi)成員重命名為不規(guī)則的名字, 通常是字母.
注意點(diǎn)
1. ProGuard僅可處理Java類(lèi)文件
ProGuard針對(duì)的是Java類(lèi)文件(Java class file
), 所以不能處理非Java類(lèi)文件, 例如xml文件, 圖片文件.
2. Android中ProGuard作用
ProGuard提供4個(gè)功能, 壓縮(shrinker), 優(yōu)化(optimizer), 混淆(obfuscator)和預(yù)校驗(yàn)(preverifier), 但是在Android中默認(rèn)會(huì)關(guān)閉優(yōu)化和預(yù)校驗(yàn)功能.
官方文檔的解釋是
Optimization is turned off by default. Dex does not like code runthrough the ProGuard optimize and preverify steps (and performs some of these optimizations on its own).
3. SDK相關(guān)的混淆處理
雖然沒(méi)有找到相關(guān)的說(shuō)明, 但是在Android Studio 2.2, Android Plugin是2.2.0的情況下, 開(kāi)啟minifyEnabled true
但是不使用proguardFiles
提供任何配置文件, 仍然會(huì)正常壓縮混淆代碼, 并且會(huì)保留Activity
, Keep
等SDK類(lèi). 但是如果你提供自己的配置文件, 那么記得加上getDefaultProguardFile('proguard-android.txt')
4. -injars
, -outjars
和-libraryjars
-injars
: 指定需要經(jīng)過(guò)ProGuard處理的文件
-outjars
: 指定經(jīng)過(guò)處理后輸出的文件名
-libraryjars
: 指定不需要經(jīng)過(guò)ProGuard處理的文件
這3個(gè)命令不會(huì)在Android中用到, 因?yàn)锳ndorid Plugin會(huì)自動(dòng)將引用的庫(kù)加入到injars
中.
5. -keep
指令
具體的用法還是建議看文檔, 戳這里
關(guān)鍵格式
-keep [,modifier,...] class_specification
值得注意的點(diǎn)
-
modifier
中可以使用includedescriptorclasses
參數(shù)來(lái)保護(hù)在類(lèi)成員提到的類(lèi)不被混淆, 例如指定保護(hù)了方法void method(Param p)
, 如果不帶這個(gè)參數(shù),Param
是可以被混淆的(沒(méi)有其他設(shè)置指明保護(hù)它的時(shí)候), 使用這個(gè)參數(shù)則可以防止Param
被混淆, 具體描述看文檔 - 在
class_specification
中, 類(lèi)和類(lèi)成員是兩種描述對(duì)象, 就是說(shuō)可以?xún)H僅保護(hù)類(lèi)但是不保護(hù)其中的類(lèi)成員, 例如-keep public class com.sample.A
, 這里僅僅指定了類(lèi), 所以類(lèi)A不會(huì)被刪除或者被混淆, 但是A里面的fields和methods則可以被刪除和被混淆; 再例如-keep public class com.sample.A{*;}
, 這里不僅保護(hù)A類(lèi), 而且保護(hù)A類(lèi)里面所有類(lèi)成員(包括fields和methods)不被刪除和混淆. -
class
關(guān)鍵字是包含了類(lèi)和接口的 - 指明方法的時(shí)候具體的參數(shù)和返回值的類(lèi)型是必須指定的, 可以使用
***
來(lái)匹配任意參數(shù)類(lèi)型
6. -keep
和-keepnames
的區(qū)別
-keep
的意思是符合條件的類(lèi)和類(lèi)成員既不會(huì)被壓縮也不會(huì)被混淆
-keepnames
是-keep,allowshrinking
的縮寫(xiě), 而allowshrinking
的意思是允許符合條件的類(lèi)和類(lèi)成員被壓縮(刪除)
7. 使用@Keep
保護(hù)特定類(lèi)和類(lèi)成員
如果引入了android.support.annotation
庫(kù)可以使用@Keep
來(lái)在代碼中保護(hù)指定的類(lèi)和類(lèi)成員.
實(shí)踐效果
- 放到類(lèi)前, 會(huì)保護(hù)類(lèi)和所有類(lèi)成員, 相當(dāng)于
-keep class A {*;}
- 放到類(lèi)成員前, 會(huì)保護(hù)類(lèi)和對(duì)應(yīng)的類(lèi)成員, 相當(dāng)于
-keepclassmembers class A {fieldType fieldName;}
- 放在Method前時(shí), 不會(huì)保護(hù)參數(shù)不被混淆
8. AndroidManifest.xml
中使用的類(lèi)
不添加額外的配置, 僅使用默認(rèn)的配置, 測(cè)試結(jié)果是會(huì)保護(hù)在AndroidManifest.xml
直接使用的類(lèi)和繼承過(guò)來(lái)的類(lèi)成員, 但是不會(huì)保護(hù)添加的類(lèi)成員
9. R文件
app的默認(rèn)編譯過(guò)程會(huì)把R文件的引用轉(zhuǎn)成具體的值, 例如如果R.layout.activity_main = 1
, 那么所有用到R.layout.activity_main
的地方都會(huì)用1
代替, 然后R文件不會(huì)包含到apk中, 因此如果有通過(guò)字符串獲取資源文件, 則需要手動(dòng)保護(hù)R文件
NOTE: 從一些地方看到的說(shuō)法是"R文件有可能不會(huì)被包含進(jìn)apk"
ProGuard QA
Q: 在Android優(yōu)化中使用-libraryjars
報(bào)錯(cuò)
Warning:Exception while processing task java.io.IOException: The same input jar some.jar is specified twice.
注: 支付寶移動(dòng)支付sdk的混淆配置
A: 因?yàn)樵?code>build.gradle中聲明依賴(lài)關(guān)系的時(shí)候一般會(huì)通過(guò)compile
命令聲明編譯某個(gè)jar包, 如
compile fileTree(include: '*.jar', dir: 'libs')
而編譯jar包相當(dāng)于-injars
命令, 而這兩個(gè)命令是沖突的, 所以只要在依賴(lài)關(guān)系中引入了某個(gè)jar包就不能再對(duì)該jar包使用-injars
或者-libraryjars
命令
Q: 報(bào)紅色warning, 提示各種InnerClasses或者EnclosingMethod****
A: 網(wǎng)上所有建議都是添加-keepattributes InnerClasses,EnclosingMethod
, 但是添加之后可以消除一些, 還是會(huì)有, 不過(guò)不影響編譯.
NOTE: 查閱一些資料之后我推測(cè)是因?yàn)镾DK編譯時(shí)候使用的JDK版本的原因?qū)е逻@些問(wèn)題, 但是不能確認(rèn)
Q: ProGuard文檔中的Entry Point的意思****
A: 使用-keep
可以使指定的類(lèi)和類(lèi)成員成為Entry Point, 其實(shí)就是掃描開(kāi)始的地方, 即使沒(méi)有其他人直接引用這個(gè)類(lèi)或者類(lèi)成員, 也保留它.
Android中ProGuard的優(yōu)化結(jié)果
優(yōu)化結(jié)果文件會(huì)輸出到<module-name>/build/outputs/mapping/release/
目錄.
-
dump.txt
: 描述APK中所有類(lèi)文件的內(nèi)部結(jié)構(gòu)(internal structure) -
mapping.txt
: 提供混淆前后類(lèi)(class)名, 方法(method)名和成員變量(field)名的對(duì)應(yīng)關(guān)系 -
seeds.txt
: 列出沒(méi)有被混淆的類(lèi)和成員(classes and members) -
usage.txt
: 列出從APK中移除的代碼(code)
分析混淆后的報(bào)錯(cuò)信息可以參考這篇文章 android-how-to-decode-proguards-obfuscated-code-from-stack-trace, 其實(shí)有個(gè)帶界面的小工具來(lái)分析報(bào)錯(cuò)信息的.
Resource shrinking壓縮資源文件
以下內(nèi)容都來(lái)自Shrink Your Resources
ProGuard不能壓縮資源文件, 所以在Android中是使用Gradle的Androidd插件中的Resource shrinking來(lái)移除沒(méi)有被使用的資源文件的, 包括庫(kù)文件內(nèi)的資源文件. 它會(huì)在ProGuard壓縮之后運(yùn)行, 所以被沒(méi)有使用的類(lèi)引用的資源文件也會(huì)被刪除.
通過(guò)
shrinkResource true
minifyEnabled true // 開(kāi)啟ProGuard是前提條件
開(kāi)啟壓縮資源文件
NOTE: Resource shrinking暫時(shí)(2016/11/2)不會(huì)對(duì)res/values
內(nèi)的文件進(jìn)行壓縮
自定義規(guī)則
創(chuàng)建一個(gè)包含<resources>
的xml文件放到資源目錄, 通過(guò)tools:keep
指定保留的資源文件, 通過(guò)tools:discard
指定明確刪除的資源文件. 指定資源文件時(shí)通過(guò),
分隔, 通過(guò)*
匹配任意字符. 例如
xmltools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
NOTE: 官方文檔中沒(méi)有說(shuō)該文件需要特定的文件名和路徑, 僅舉例res/raw/keep.xml
. 該文件不會(huì)被放進(jìn)最后的apk文件中.
被動(dòng)態(tài)使用的資源
Android中可以通過(guò)[Resources.getIdentifier()](https://developer.android.com/reference/android/content/res/Resources.html#getIdentifier(java.lang.String, java.lang.String, java.lang.String))來(lái)通過(guò)字符串匹配來(lái)動(dòng)態(tài)獲取某個(gè)資源文件id, 當(dāng)使用了這個(gè)方法時(shí)(v7 appcompat library
中使用了), Resource shrinking不會(huì)壓縮符合字符串規(guī)則的文件.
可以通過(guò)tools:shrinkMode="strict"
來(lái)關(guān)閉這個(gè)默認(rèn)行為, 指明只有明確被引用的資源才保留, 代碼中動(dòng)態(tài)引用的資源則默認(rèn)不保留.
Resource shrinking壓縮結(jié)果
資源文件壓縮后可以通過(guò)<module-name>/build/outputs/mapping/release/resources.txt
來(lái)查看所有資源文件的關(guān)系.
aar庫(kù)包含混淆規(guī)則
參考生成帶混淆配置的aar庫(kù)
關(guān)鍵屬性是consumerProguardFiles
庫(kù)的build.gradle中配置類(lèi)似
android {
defaultConfig {
minifyEnabled true
consumerProguardFiles 'consumer-proguard-rules.pro'
}
}
注意, 因?yàn)閱⒂昧?code>minifyEnabled, 因此編譯庫(kù)時(shí)會(huì)混淆整個(gè)lib, 這樣會(huì)導(dǎo)致外部工程不能正常引用庫(kù)中的類(lèi), 因?yàn)轭?lèi)名被混淆了, 所以需要添加混淆規(guī)則, 確保庫(kù)暴露給外部的API相關(guān)類(lèi)不被混淆
END
這邊文章的主要目的是分析ProGuard優(yōu)化Android代碼時(shí)遇到的問(wèn)題, 歡迎在討論區(qū)指出不明白的地方或者提出你遇到的問(wèn)題, 大家一起研究.