Android實(shí)踐:使用ProGuard壓縮和混淆Apk

本文主要討論對(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)

  1. 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被混淆, 具體描述看文檔
  2. 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)不被刪除和混淆.
  3. class關(guān)鍵字是包含了類(lèi)和接口的
  4. 指明方法的時(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í)踐效果

  1. 放到類(lèi)前, 會(huì)保護(hù)類(lèi)和所有類(lèi)成員, 相當(dāng)于-keep class A {*;}
  2. 放到類(lèi)成員前, 會(huì)保護(hù)類(lèi)和對(duì)應(yīng)的類(lèi)成員, 相當(dāng)于-keepclassmembers class A {fieldType fieldName;}
  3. 放在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/目錄.

  1. dump.txt : 描述APK中所有類(lèi)文件的內(nèi)部結(jié)構(gòu)(internal structure)
  2. mapping.txt : 提供混淆前后類(lèi)(class)名, 方法(method)名和成員變量(field)名的對(duì)應(yīng)關(guān)系
  3. seeds.txt : 列出沒(méi)有被混淆的類(lèi)和成員(classes and members)
  4. 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)題, 大家一起研究.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市桐腌,隨后出現(xiàn)的幾起案子绘沉,更是在濱河造成了極大的恐慌登渣,老刑警劉巖松嘶,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钮热,死亡現(xiàn)場(chǎng)離奇詭異囚玫,居然都是意外死亡实檀,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)衬横,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)裹粤,“玉大人,你說(shuō)我怎么就攤上這事蜂林∫K撸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵噪叙,是天一觀的道長(zhǎng)矮锈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)睁蕾,這世上最難降的妖魔是什么愕难? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮惫霸,結(jié)果婚禮上猫缭,老公的妹妹穿的比我還像新娘。我一直安慰自己壹店,他們只是感情好猜丹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著硅卢,像睡著了一般射窒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上将塑,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天脉顿,我揣著相機(jī)與錄音,去河邊找鬼点寥。 笑死艾疟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蔽莱,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼弟疆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了盗冷?” 一聲冷哼從身側(cè)響起怠苔,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仪糖,沒(méi)想到半個(gè)月后柑司,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锅劝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年帜羊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸠天。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖帐姻,靈堂內(nèi)的尸體忽然破棺而出稠集,到底是詐尸還是另有隱情,我是刑警寧澤饥瓷,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布剥纷,位于F島的核電站,受9級(jí)特大地震影響呢铆,放射性物質(zhì)發(fā)生泄漏晦鞋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一棺克、第九天 我趴在偏房一處隱蔽的房頂上張望悠垛。 院中可真熱鬧,春花似錦娜谊、人聲如沸确买。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)湾趾。三九已至,卻和暖如春派草,著一層夾襖步出監(jiān)牢的瞬間搀缠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工近迁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留艺普,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像衷敌,于是被迫代替她去往敵國(guó)和親勿侯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容