1. 程式人生 > >帶你徹底明白 Android Studio 打包混淆

帶你徹底明白 Android Studio 打包混淆

前言

在使用Android Studio混淆打包時,該IDE自身集成了Java語言的ProGuard作為壓縮,優化和混淆工具,配合Gradle構建工具使用很簡單。只需要在工程應用目錄的gradle檔案中設定minifyEnabled為true即可。然後我們就可以到proguard-rules.pro檔案中加入我們的混淆規則了。

ProGuard作用

壓縮(Shrinking):預設開啟,用以減小應用體積,移除未被使用的類和成員,並且會在優化動作執行之後再次執行(因為優化後可能會再次暴露一些未被使用的類和成員)。如果想要關閉壓縮,在proguard-rules.pro檔案中加入:

# 關閉壓縮
-dontshrink 
  • 1
  • 2

優化(Optimization):預設開啟,在位元組碼級別執行優化,讓應用執行的更快。同上,如果想要關閉優化,在proguard-rules.pro檔案中加入:

# 關閉優化
-dontoptimize
-optimizationpasses n 表示proguard對程式碼進行迭代優化的次數,Android一般為5
  • 1
  • 2
  • 3

混淆(Obfuscation):預設開啟,增大反編譯難度,類和類成員會被隨機命名,除非用keep保護。

# 關閉混淆
-dontobfuscate
  • 1
  • 2

混淆後預設會在工程目錄app/build/outputs/mapping/release下生成一個mapping.txt檔案,這就是混淆規則,我們可以根據這個檔案把混淆後的程式碼反推回源本的程式碼,所以這個檔案很重要,注意保護好。原則上,程式碼混淆後越亂越無規律越好,但有些地方我們是要避免混淆的,否則程式執行就會出錯,所以就有了下面要教大家的,如何讓自己的部分程式碼避免混淆從而防止出錯。

實現程式碼混淆

使用Android Studio正式打包時預設是不開啟程式碼混淆的,如果需要開啟程式碼混淆,可以在 app 模組下的build.gradle 檔案中修改 minifyEnabled false 為 true。程式碼結構如下:

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.wiwide.soldout"
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 6
versionName "2.0.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { // 是否進行混淆 minifyEnabled true // 混淆檔案的位置 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { ··· } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

修改完這裡,需要開啟app模組下的 proguard-rules.pro 檔案來定義專案打包的混淆選項:(借鑑別人的一個混淆模板,大部分通用,有個別不需要的可以去掉)

#--------------------------1.實體類---------------------------------
# 如果使用了Gson之類的工具要使被它解析的JavaBean類即實體類不被混淆。(這裡填寫自己專案中存放bean物件的具體路徑)
-keep class com.wiwide.soldout.bean.**{*;}

#--------------------------2.第三方包-------------------------------

#Gson
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.* { *;}
-dontwarn com.google.gson.**

#butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

#-------------------------3.與js互相呼叫的類------------------------


#-------------------------4.反射相關的類和方法----------------------


#-------------------------5.基本不用動區域--------------------------
#指定程式碼的壓縮級別
-optimizationpasses 5

#包明不混合大小寫
-dontusemixedcaseclassnames

#不去忽略非公共的庫類
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers

#混淆時是否記錄日誌
-verbose

#優化  不優化輸入的類檔案
-dontoptimize

#預校驗
-dontpreverify

# 保留sdk系統自帶的一些內容 【例如:-keepattributes *Annotation* 會保留Activity的被@override註釋的onCreate、onDestroy方法等】
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod

# 記錄生成的日誌資料,gradle build時在本項根目錄輸出
# apk 包內所有 class 的內部結構
-dump proguard/class_files.txt
# 未混淆的類和成員
-printseeds proguard/seeds.txt
# 列出從 apk 中刪除的程式碼
-printusage proguard/unused.txt
# 混淆前後的對映
-printmapping proguard/mapping.txt


# 避免混淆泛型
-keepattributes Signature
# 丟擲異常時保留程式碼行號,保持原始檔以及行號
-keepattributes SourceFile,LineNumberTable

#-----------------------------6.預設保留區-----------------------
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclassmembers 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*(***);
}

#保持 Serializable 不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 保持自定義控制元件類不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet);
}
# 保持自定義控制元件類不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet,int);
}
# 保持自定義控制元件類不被混淆
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

# 保持列舉 enum 類不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

# 不混淆R檔案中的所有靜態欄位,我們都知道R檔案是通過欄位來記錄每個資源的id的,欄位名要是被混淆了,id也就找不著了。
-keepclassmembers class **.R$* {
    public static <fields>;
}

#如果引用了v4或者v7包
-dontwarn android.support.**

# 保持哪些類不被混淆
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-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.preference.Preference

-keep class com.zhy.http.okhttp.**{*;}
-keep class com.wiwide.util.** {*;}

# ============忽略警告,否則打包可能會不成功=============
-ignorewarnings
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137

混淆的一些基本規則:

先看如下兩個比較常用的命令,很多童鞋可能會比較迷惑以下兩者的區別。

-keep class cn.hadcn.test.**
-keep class cn.hadcn.test.*
  • 1
  • 2

一顆星 
  表示只是保持該包下的類名,而子包下的類名還是會被混淆; 
兩顆星 
  表示把本包和所含子包下的類名都保持;用以上方法保持類後,你會發現類名雖然未混淆,但裡面的具體方法和變數命名還是變了。 
   
這時如果既想保持類名,又想保持裡面的內容不被混淆,我們就需要以下方法了:

-keep class cn.hadcn.test.* {*;}
  • 1

在此基礎上,我們也可以使用Java的基本規則來保護特定類不被混淆,比如我們可以用extends,implements等這些Java規則。如下例子就避免所有繼承Activity的類被混淆

-keep public class * extends android.app.Activity
  • 1

如果我們要保留一個類中的內部類不被混淆則需要用$符號,如下例子表示保持ScriptFragment內部類JavaScriptInterface中的所有public內容不被混淆。

-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
   public *;
}
  • 1
  • 2
  • 3

再者,如果一個類中你不希望保持全部內容不被混淆,而只是希望保護類下的特定內容,就可以使用

<init>;     //匹配所有構造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法
  • 1
  • 2
  • 3

你還可以在<fields><methods>前面加上private 、public、native等來進一步指定不被混淆的內容,如

-keep class cn.hadcn.test.One {
    public <methods>;
}
  • 1
  • 2
  • 3

表示One類下的所有public方法都不會被混淆,當然你還可以加入引數,比如以下表示用JSONObject作為入參的建構函式不會被混淆

-keep class cn.hadcn.test.One {
   public <init>(org.json.JSONObject);
}
  • 1
  • 2
  • 3

有時候你是不是還想著,我不需要保持類名,我只需要把該類下的特定方法保持不被混淆就好,那你就不能用keep方法了,keep方法會保持類名,而需要用keepclassmembers ,如此類名就不會被保持,為了便於對這些規則進行理解,官網給出了以下表格:

保留防止被移除或者被重新命名防止被重新命名
類和類成員-keep-keepnames
僅類成員-keepclassmembers-keepclassmembernames
如果擁有某成員,保留類和類成員-keepclasseswithmembers-keepclasseswithmembernames
# -keep關鍵字
# keep:包留類和類中的成員,防止他們被混淆
# keepnames:保留類和類中的成員防止被混淆,但成員如果沒有被引用將被刪除
# keepclassmembers :只保留類中的成員,防止被混淆和移除。
# keepclassmembernames:只保留類中的成員,但如果成員沒有被引用將被刪除。
# keepclasseswithmembers:如果當前類中包含指定的方法,則保留類和類成員,否則將被混淆。
# keepclasseswithmembernames:如果當前類中包含指定的方法,則保留類和類成員,如果類成員沒有被引用,則會被移除。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意事項

1、自己測試的時候使用的是Android Studio 2.3.3 版本,在混淆打包時,每次構建時 ProGuard 都會輸出下列檔案:

檔案作用
dump.txt說明 APK 中所有類檔案的內部結構。
mapping.txt提供原始與混淆過的類、方法和欄位名稱之間的轉換。
seeds.txt列出未進行混淆的類和成員。
usage.txt列出從 APK 移除的程式碼。


這些檔案儲存在 /build/outputs/mapping/release/ 中。(所以筆者認為上邊的混淆模板中的記錄生成的日誌資料沒有新增的必要,因為IDE在編譯時自動已經輸出了)

2、jni方法不可混淆,因為這個方法需要和native方法保持一致;

# 保持native方法不被混淆 
-keepclasseswithmembernames class * {    
    native <methods>;
}
  • 1
  • 2
  • 3
  • 4

3、反射用到的類不混淆(否則反射可能出現問題);

4、AndroidMainfest中的類不混淆,所以四大元件和Application的子類和Framework層下所有的類預設不會進行混淆,自定義的View預設也不會被混淆。所以像網上貼的很多排除自定義View,或四大元件被混淆的規則在Android Studio中是無需加入的。

5、與服務端互動時,使用GSON、fastjson等框架解析服務端資料時,所寫的JSON物件類不混淆,否則無法將JSON解析成對應的物件;

6、使用第三方開源庫或者引用其他第三方的SDK包時,如果有特別要求,也需要在混淆檔案中加入對應的混淆規則;

7、有用到WebView的JS呼叫也需要保證寫的介面方法不混淆,原因和第一條一樣;

8、Parcelable的子類和Creator靜態成員變數不混淆,否則會產生Android.os.BadParcelableException異常;

# 保持Parcelable不被混淆    
-keep class * implements Android.os.Parcelable {         
    public static final Android.os.Parcelable$Creator *;
}
  • 1
  • 2
  • 3
  • 4

9、使用enum型別時需要注意避免以下兩個方法混淆,因為enum類的特殊性,以下兩個方法會被反射呼叫,見第二條規則。

-keepclassmembers enum * {  
    public static **[] values();  
    public static ** valueOf(java.lang.String);  
}
  • 1
  • 2
  • 3
  • 4

寫在最後

釋出一款應用除了設minifyEnabled為ture,你也應該設定zipAlignEnabled為true,像Google Play強制要求開發者上傳的應用必須是經過zipAlign的,zipAlign可以讓安裝包中的資源按4位元組對齊,這樣可以減少應用在執行時的記憶體消耗。

本人能力有限,如果此博文中有哪裡講得讓人難以理解,歡迎留言交流,若有講解錯的地方歡迎指出,大家互相學期,共同進步!

相關推薦

徹底明白 Android Studio 打包混淆

前言在使用Android Studio混淆打包時,該IDE自身集成了Java語言的ProGuard作為壓縮,優化和混淆工具,配合Gradle構建工具使用很簡單。只需要在工程應用目錄的gradle檔案中設定minifyEnabled為true即可。然後我們就可以到proguar

Android Studio打包混淆資源的SDK

           最近要實現一個把自己的整個應用打包成SDK接入到合作公司的應用中,剛開始是想採用外掛(如360的DroidPlugin,原理解析連結:分析DroidPlugin,深入理解外掛化框架)的形式來做,這樣的話很方便,只要提供一個apk就行了。但是問題來了,一

Android studio打包混淆編譯的時候出現異常:transformClassesAndResourcesWithProguardForRelease

具體異常資訊擷取如下: Browser/build/intermediates/exploded-aar/com.google.android.gms/play-services-analytics/7.8.0/jars/classes.jar(;;;;;;**.clas

如何使用Android Studio打包混淆的Jar

使用AS打包混淆Jar包,百度一下,一片一片的,但是很多都是零零散散的寫得不是很詳細或是直接拷貝,按照他們的教程測試總不是很順利,所以這裡我就把我個人學習AS打包混淆Jar的成果總結出來,希望對大家有幫助。個人覺得寫得還是比較詳細的 使用gradle混淆打包Jar 使

徹底看懂React Native和Android原生控制元件之間的對映關係

此文基於react natve的 September 2018 - revision 5 版本 本人學校畢業後就當了安卓爬坑專業戶,3年來總算爬習慣了,不料今年掉進了RN這個天坑,從此開始了我的悲慘人生。。。Anyway,RN的思想還是值得學習的,今天就從Android的角度開始分析一下react nati

Android設計模式之一個例子讓徹底明白工廠模式(Factory Pattern)

提出疑問 這幾天研究工廠模式的時候,看到網上的一些文章中舉的例子我就很疑惑,我相信這也是許多人的疑惑:工廠模式的功能就是建立例項,我們建立例項直接new不就完了嗎,幹嘛還得再封裝一層工廠類,然後用工廠類再去new出這個例項?這不多此一舉嗎? 比如我看到這樣的

Android設計模式之一個例子讓徹底明白裝飾者模式(Decorator Pattern)

導讀 這篇文章中我不會使用概念性文字來說明裝飾者模式,因為通常概念性的問題都很抽象,很難懂,使得讀者很難明白到底為什麼要使用這種設計模式,我們設計模式的誕生,肯定是前輩們在設計程式的時候遇到了某種困難,為了避免這種苦難的發生,從而設計出來的這種設計模式,所以這

全方位徹底搞懂Android記憶體洩露

1Java記憶體回收方式 Java判斷物件是否可以回收使用的而是可達性分析演算法。 在主流的商用程式語言中(Java和C#),都是使用可達性分析演算法判斷物件是否存活的。

Android Studio 打包成jar檔案並混淆程式碼

參考博文:http://www.jianshu.com/p/0a3ce6e9ab85 開展專案合作時,基於模組化思想,對方要用到你的程式,而你又不想將原始碼給對方,通常會將程式進行打包生成jar,並作混淆處理。 1、建立專案 【File】——【New Module】——【A

Android 徹底理解 Window 和 WindowManager

有時候我們需要在桌面上顯示一個類似懸浮窗的東西,這種效果就需要用 Window 來實現,Window 是一個抽象類,表示一個視窗,它的具體實現類是 PhoneWindow,實現位於 WindowManagerService 中。相信看到 WindowManage

【python測試開發棧】徹底明白python3編碼原理

在之前的文章中,我們介紹過編碼格式的發展史:[文章傳送門-todo]。今天我們通過幾個例子,來徹底搞清楚python3中的編碼格式原理,這樣你之後寫python指令碼時碰到編碼問題,才能有章可循。 我們先搞清楚幾個概念: 系統預設編碼:指python直譯器預設的編碼格式,在python檔案頭部沒有宣告其他編

Android Studio打包以及Gradle配置構建

otto sign rac color conf wmf git var png 本文轉載 郭霖公眾號 https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650241610&idx=1&sn

Android Studio 打包生成 APK

如果 sign ext 對話 http ner 分享 技術 oid 1. 第一步 Build -> Generate Signed APK 2. 之後會要求開發者輸入相關的密鑰文件和密碼 如果有則找到對應的 .jks 文件輸入密碼完成相應操作,否則則創建一個對

Android Studio 打包生成apk

self one 直接 word uil 編譯 jpg content 但是 打開AndroidStudio,並且打開想要生成apk文件的項目。 點擊工具欄上面的“Builder” 點擊“Builder”之後在下拉菜單裏面可以看到“Genara

【轉】Android Studio打包全攻略---從入門到精通

UC store 類型 安裝文件 public alt url tool 描述 原文地址:http://blog.csdn.net/zivensonice/article/details/51672846 初出茅廬 手動打包 怎麽手動打包 項目寫完了,現在需要把應用上傳

徹底明白JAVA中堆與棧的區別

java程序 運行 一點 動態 自動變 key 空間 類型 lin 原文地址:http://www.2cto.com/kf/201302/190704.html 簡單的說: Java把內存劃分成兩種:一種是棧內存,一種是堆內存。 在函數中定義的一些基本類型的變量和對象的引用

Android Studio 打包AAR和第三方靜態庫

contain ise ply appcompat pla 依賴 prop 三方庫 spa 需求 現在有一個第三方庫libstatic_add.a和對應的頭文件static.h,要求封裝一個Module,該Module依賴這個靜態庫,要求打包的Module包含該靜態庫。

BAT大牛 深度剖析Android 10大開源框架

安卓第1章 課程介紹(提供bat內推和簡歷指導)1-1 課程導學第2章 Okhttp網絡庫深入解析和相關面試題分析2-1 okhttp框架流程分析2-2 okhttp同步請求方法2-3 okhttp異步請求方法2-4 okhttp同步請求流程和源碼分析2-5 okhttp異步請求流程和源碼分析-12-6 ok

Android Studio打包生成APK教程

src 變量名 nts .apk variants idt test filename 一個 一、修改版本和指定生成APK文件名【可選】 將項目切換到Project視圖,打開app目錄下的build.gradle文件 1.1 修定軟件版本 versionCode是app的大

[精]--這一次,讓徹底明白Java的值傳遞和引用傳遞!

本文旨在用最通俗的語言講述最枯燥的基本知識 學過Java基礎的人都知道:值傳遞和引用傳遞是初次接觸Java時的一個難點,有時候記得了語法卻記不得怎麼實際運用,有時候會的了運用卻解釋不出原理,而且坊間討論的話題又是充滿爭議:有的論壇帖子說Java只有值傳遞,有的部落格說兩者皆有;這讓人有點摸不著頭