一直以來,在項目中須要進行代碼混淆時每次都要去翻文檔,很麻煩。也沒有像寫代碼那樣記得那麼多。既然要查來查去,就不如本身捋一捋這個知識點了,被人寫的終究仍是別人的。因此本身去翻看了不少文章和官方文檔,總結下就把這篇文章寫下來了。之後方便查找和修改,也加深這個知識的理解。javascript
Android 開發中,打包避免不了各類優化,開啓混淆能夠很好就是其中一種優化方式。爲了使你打包的 apk 儘量小,應該在打包 apk 的時候開啓代碼壓縮功能移除沒有被使用的代碼和資源。可是這和混淆有什麼關係?php
代碼壓縮混淆:ProGuard 能夠從你打包的應用程序檢測和刪除未使用的類、字段、方法和屬性,固然也會包括libraries裏的,這樣對64K引用限制頗有幫助的。ProGuard 也會優化字節碼,把類、字段、屬性和方法用短的名稱表示-混淆(官方文檔上也說了還有刪除未使用的代碼指令,這個有時間再研究下了)。
資源壓縮:(Resource shrinking) 配合 Gradle 使用,能夠安全移除沒有使用的打包時應用程序沒有使用到的資源,包括代碼庫中的資源。css
那麼混淆 (ProGuard) 改怎麼用?html
android {
buildTypes {
release {
minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
上面這段代碼很眼熟啊,就是項目 build.gradle 文件裏的一段代碼,不過項目建立時默認 minifyEnabled
的值是 false
,須要開發者手動改成 true
,或者點擊這樣:java
選中項目例如app-按F4-進入 Project Structure -右邊選擇 Build Type -選擇 release -把Minify Enable
設置爲true
linux
看下兩個分別打包出來的效果:本身感覺下,能夠看到先後apk大小和代碼的變化android
若是留意 gradle 控制檯的換,會有:proguard:xxxx
的信息。代碼就這麼被混淆和壓縮了,原本不少有意義的單詞都被一些字母代替了。數組
每次build完以後,ProGuard 都會輸出一些文件在項目模塊 /build/outputs/mapping/release/
下:安全
關於
mapping.txt
使用的場景也是比較多的,好比使用bugly
等平臺可能須要上傳mapping或者用Android Studio
查看 apk 的 classes.dex 時,有須要的話可使用Load ProGuard mappings...
加載mapping.txt
,這樣能夠看到混淆前的代碼樣子bash
注意:若是在debug模式下設置
minifyEnabled true
,並且須要使用Instant Run
增量構建的時候,ProGuard只會刪除不使用的代碼,不會混淆代碼。不過這個狀況也是在debug下使用的吧,通常在debug模式下不開啓混淆的,由於項目build的時間就很長了,再開個混淆那就更長了。若是你真的要這麼debug的話:用useProguard false
便可。這樣代碼也會被混淆。
這個需求我暫時沒有遇到過,只是在debug下有開啓過混淆,可是沒有用Instant Run
。
代碼以下:
android {
buildTypes {
debug {
minifyEnabled true useProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } ... }
android {
...
buildTypes {
release {
shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
加了一句shrinkResources true
。但是在用 Android studio 打開 apk 的時候你仍是發現有那個文件的存在,不過是以前的那個文件被替換成了 1x1(67B) 大小的圖片了。
並非全部代碼都須要被混淆的,通常默認的 ProGuardFile <sdk>\tools\proguard\proguard-android.txt
下已經默認作好了一些設置,哪些些代碼不能被混淆的,可是在實際開發中可能還不夠,還須要開發者自定義一些混淆規則。在這裏我有個小建議:若是須要添加第三方庫到你的 app,那麼同時順便也找下這個庫的混淆規則,並把它加入到你的項目裏。混淆規則通常遵循如下幾點:
1. 使用註解 @Keep
如下代碼的效果是類和成員都會保留。只想保留成員能夠在成員上使用@keep
@Keep public class User { private String userName; public String getUserName() { return userName; } }
哪些類、方法、屬性、字段須要保留的,不被混淆的,用@Keep
能夠了,就是這麼簡單,這個幾乎沒怎麼用。
2. 在 proguard-rules.pro 中定義
這個文件在項目的根目錄下。
這個是用得比 @Keep 用都多,好比添加第三庫的時候,通常會提供相應的混淆規則,這些規則須要加入到混淆文件中。
-keep
: 保留指定的類和成員(字段、方法)。-keepclassmembers
:保留指定的類成員,若是成員所在的類也保留下來的話。也就是說類沒有保留,那麼成員也不存在了或者說這個命令不能影響類。-keepclasseswithmembers
:保留指定的類和成員,前提條件是所指定類中要有的指定的類成員。好比: User 類中沒有 name,可是你指定保留 name,那麼這個命令是不生效的。-keepnames
:-keep,allowshrinking
的簡寫,指定保留指定類和成員,若是代碼壓縮階段類或成員沒有被刪除。-keepclassmembernames
:-keepclassmembers,allowshrinking
的簡寫,保留指定的成員,若是代碼壓縮階段成員沒有被刪除。-keepclasseswithmembernames
:-keepclasseswithmembers,allowshrinking
的簡寫,保留指定類和成員,若是代碼壓縮後壓縮沒有被刪除。好比:User 類中沒有 name,可是你指定保留 name,那麼這個命令是不生效的(User 該混淆混淆)。-dontwarn
:指定不警告未解決的引用和其餘重要問題。-keepattributes
:-keepattributes SourceFile,LineNumberTable
和 -renamesourcefileattribute SourceFile
配合使用。分別是 保存用於調試堆棧跟蹤的行號信息 和 隱藏原始的源文件名。在項目的 ProGuard 文件取消註釋就能夠用了。-keepattributes
其餘屬性 說明:allowshrinking
表示容許被刪除。相似的修飾符還有 其餘的
保留項 | 不會被移除(壓縮)、混淆(重命名) | 會被移除(壓縮)、混淆(重命名) |
---|---|---|
類、成員 | -keep |
-keepnames |
成員 | -keepclassmembers |
-keepclassmembernames |
類、成員(若是有指定成員) | -keepclasseswithmembers |
-keepclasseswithmembernames |
使用規則
[命令] [類規範]{ [成員]; }
注意:須要不須要保留成員,能夠不用{[成員];}
[註解] [修飾符] 類定義符 類名 [extends | implements 類名]
中括號裏的是可選項,分割線|
提供選擇,具體以下:
public
| private
| final
| abstract
| ...
,前加 !
表示非xxxinterface
| class
| enum
,interface
| enum
前加 !
表示非interface
| enum
,註解用class
表示?
- 任何單個字符;*
- 不包括包分隔符的其餘任何部分;**
- 任何部分;
。{
[@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> | (fieldtype fieldname); [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> | <init>(argumenttype,...) | classname(argumenttype,...) | (returntype methodname(argumenttype,...)); [@annotationtype] [[!]public|private|protected|static ... ] *; ... } 簡單的就是: { [註解] [修飾符] <fields> | 類型 字段名; [註解] [修飾符] <methods> | 方法 | <init>(參數類型,...) | classname(參數類型,...) | 返回值類型 方法名(參數,...); [註解] [修飾符] *; ... }
!
表示非xxx%
-任何基本數據類型;?
-任何單個字符;*
-不包括包分隔符的其餘任何部分;**
-任何部分;***
-任何類型(數組等);...
-任意長度任意類型參數-dontwarn javax.annotation.**
-keep public class com.xin.proguard.nonuse.Keep{ *; }
-keep public class com.xin.proguard.nonuse.**{ *; }
Bean
的類及其成員-keep class **Bean**{ *; }
-keep class * implements com.bumptech.glide.module.GlideModule
-keep class * extends com.bumptech.glide.module.AppGlideModule
-keepclassmembers class android.support.design.internal.BottomNavigationMenuView{ private boolean mShiftingMode; }
-keepclassmembers class com.xin.proguard.nonuse.User{ public void setUserName(java.lang.String); }
build.gradle
配置以下,緣由是配置 -dontoptimize
狀況下沒法移除log打印。buildTypes {
release {
shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }
-assumenosideeffects class android.util.Log{ public static *** v(...); public static *** i(...); public static *** d(...); public static *** w(...); public static *** e(...); }
大膽的建議:若是很明確某個包或者某個類改怎麼處理的話,用
-keepnames
比-keep
好。
使用前請肯定在項目 build.gradle
中配置了 shrinkResources true
。自定義資源保留規則須要在項目的 xml 文件中定義:根節點 <resources>
,須要保留資源用 tools:keep
,須要移除資源用 tools:discard
;兩個資源之間能夠用 ,
分隔、*
能夠用做通配符。
可能有人會問:若是我把同一個資源同時做爲 tools:keep
和 tools:discard
的值,這咋整?告訴你,這個資源會被移除。固然,誰會這麼傻逼作這樣的事呢,哈哈哈。不要問我是怎麼知道的。說多都是淚,我就是那個傻逼
嚴格模式:tools:shrinkMode="strict"
。tools:shrinkMode
默認值是safe
,默認狀況若是在代碼中使用Resources.getIdentifier()
,混淆器會把全部匹配的資源都標記爲已使用的,且不可刪除。
好比:下面的代碼會致使全部以img_
爲前綴的資源都會被標記爲「已使用」
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
例如:在res/raw/
下建立keep.xml
。keep.xml
內容以下
在res/其它文件夾下建立其餘名稱的xml文件也行
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2,@layout/unused3" />
若是開啓了嚴格模式,並且也在代碼中動態獲取資源,那麼就要使用tools:keep
屬性保留指定的資源。不然使用代碼獲取的資源不能正常使用。
項目構建的時候不會把 keep.xml 打包到 apk 裏。不是 apk 裏沒有 keep.xml 文件,而是
keep.xml 裏只剩個空殼。
刪除可替代資源
資源壓縮能夠刪除未使用的代碼和資源,一樣也能夠刪除一些可替代的資源。好比國際化中一些語言資源string.xml
、values-xx
,佈局資源layout-xx
等。當你不須要所有語言時,根據實際須要把指定的語言打包到apk裏,那麼資源壓縮器就能夠幫到你了。你能夠在項目的gradle
文件配置resConfig
(resConfigs
)屬性,只保留顯示聲明的,未聲明的將被刪除。
例如如下代碼展現只是用語言資源爲中文:
android {
defaultConfig {
...
resConfig "zh" } }
若是是多個資源那就把 resConfig
改成 resConfigs
多了個 s
便可,固然,只聲明一個資源時也能夠用 resConfigs
。例如,只保留英語、法語、豎屏佈局資源:
android {
defaultConfig {
...
resConfigs "en", "fr", "port" } }
屏幕密度資源配置請參考這裏configure-split
關於 Android 混淆和資源壓縮就到此結束了,好久沒寫東西了,寫得不要請見諒,有不妥之處歡迎指出。感謝閱讀。
參考連接