Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 安卓與“Proguard”——安卓的代碼混淆

安卓與“Proguard”——安卓的代碼混淆

編輯:關於Android編程

謹以此文,記我在公司實習時,所接到的第一個正式的、真正有意義的任務——將公司即將發布的APK進行代碼混淆。

什麼是代碼混淆

混淆就是對發布出去的程序進行重新組織和處理,使得處理後的代碼與處理前代碼完成相同的功能,而混淆後的代碼很難被反編譯,即使反編譯成功也很難得出程序的真正語義。被混淆過的程序代碼,仍然遵照原來的檔案格式和指令集,執行結果也與混淆前一樣,只是混淆器將代碼中的所有變量、函數、類的名稱變為簡短的英文字母代號,在缺乏相應的函數名和程序注釋的況下,即使被反編譯,也將難以閱讀。同時混淆是不可逆的,在混淆的過程中一些不影響正常運行的信息將永久丟失,這些信息的丟失使程序變得更加難以理解。

 

為什麼要進行代碼混淆

>Java 是一種跨平台的、解釋型語言,Java 源代碼編譯成中間”字節碼”存儲於 class 文件中。由於跨平台的需要,Java 字節碼中包括了很多源代碼信息,如變量名、方法名,並且通過這些名稱來訪問變量和方法,這些符號帶有許多語義信息,很容易被反編譯成 Java 源代碼。為了防止這種現象,我們可以使用 Java 混淆器對 Java 字節碼進行混淆。 >混淆器的作用不僅僅是保護代碼,它也有精簡編譯後程序大小的作用。以“ProGuard”為例,“ProGuard”的主要作用就是混淆,同時它還能對字節碼進行縮減體積、優化等。由於對變量名和方法名進行縮減,以及前面所說過的部分不影響正常運行的信息會被丟失,使得編譯後的jar文件的體積減少,文件精簡。  

通過什麼方式進行代碼混淆

目前,主流的代碼混淆的方式,就是使用“Proguard”,同時,你也可以在網上搜代碼混淆,代碼加固等等服務,可以輕易的搜索到諸如“360加固”、“愛加密”之類第三方的服務。那麼他們的區別在哪裡: 1>使用工具,就是在你自己的電腦上,通過配置“Proguard”之類的工具,自己進行代碼混淆,自己編譯,自己調試。而使用第三方服務,就是將你自己的APK,上傳到他們的網站,他們幫你進行混淆,混淆/加固完成,再將混淆後的APK發回來給你。 2>使用工具,APK自始至終都在你自己手裡,如果使用第三方的服務,原始APK就需要傳給別人,這樣增加了不安全性,當然你有可能會說,別人那麼大間公司怎麼觊觎你的APK?對於這個問題的看法就因人而異,像我實習那時處理我公司的那個項目,我的“leader”明確要求自己使用工具手動混淆,不能使用第三方的服務,不能將APK傳給別人。  

什麼是“ProGuard”

“ProGuard”是一個混淆代碼的開源項目。它的主要作用就是混淆,當然它還能對字節碼進行縮減體積、優化等,但是對於我們來說,體積壓縮以及優化功能,還不是最重要的。我們真正在乎的,就是他的混淆功能。  

“ProGuard”可以進行哪些優化?

以下資料,來自網絡:

除了在壓縮操作刪除的無用類,字段和方法外,“ProGuard”也能在字節碼級提供性能優化,內部方法有: >常量表達式求值。 >刪除不必要的字段存取。 >刪除不必要的方法調用。 >刪除不必要的分支。 >刪除不必要的比較和instanceof驗證。 >刪除未使用的代碼。 >刪除只寫字段。 >刪除未使用的方法參數。 >像push/pop簡化一樣的各種各樣的peephole優化。 >在可能的情況下為類添加static和final修飾符。 >在可能的情況下為方法添加private, static和final修飾符。 >在可能的情況下使get/set方法成為內聯的。 >當接口只有一個實現類的時候,就取代它。 >選擇性的刪除日志代碼。

實際的優化效果是依賴於你的代碼和執行代碼的虛擬機的。簡單的虛擬機比有復雜JIT編譯器的高級虛擬機更有效。無論如何,你的字節碼會變得更小。
仍有一些明顯需要優化的技術不被支持:

>使非final的常量字段成為內聯。

>像get/set方法一樣使其他方法成為內聯。

>將常量表達式移到循環之外。

 

如何使用“ProGuard”

前面說了混淆代碼的起因和意義,也介紹了“ProGuard”各種好處,現在說說怎麼使用這個工具。首先,以下說明全部基於"Eclipse"開發環境,Android2.3以後版本。

在Android 2.3以前,混淆Android代碼只能手動添加proguard來實現代碼混淆,非常不方便。而2.3以後,Google已經將這個工具加入到了SDK的工具集裡。該工具的具體路徑:SDK\tools\proguard。當創建一個新的Android工程時,在工程目錄的根路徑下,會出現一個proguard的配置文件proguard.cfg。也就是說,我們可以通過簡單的配置,在我們的elipse工程中直接使用ProGuard混淆Android工程。

如何啟動“ProGuard”

在工程的根路徑下,找到”project-properties.txt”文件,源碼如下:

# This file is automaticallygenerated by Android Tools.

# Do not modify this file -- YOURCHANGES WILL BE ERASED!

#

# This file must be checked inVersion Control Systems.

#

# To customize properties used bythe Ant build system edit

# "ant.properties", andoverride values to adapt the script to your

# project structure.

#

# To enable ProGuard to shrink andobfuscate your code,uncomment this (available properties: sdk.dir,user.home):

# proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

 

# Project target.

target=android-22

你只要將藍色那段代碼,前面那個“#”號去掉,就能啟動“ProGuard”工具,當然藍色不是這個文件裡面這段代碼原有顏色,是我在文章裡面為了說明加上的。現在進行導包操作,在導包的過程當中,“Eclipse”會自動使用“ProGuard”工具。另外需要說明的一點是,你直接“run”到設備的應用是沒有經過混淆的,即便你已經啟動了“ProGuard”工具,只有手動導包之後所獲得的APK,才是經過混淆工具處理過的。  

如何配置“ProGuard”混淆文件

前面說了如何啟動“ProGuard”工具,對於一些極簡單的工程(沒有引用第三方庫,沒有自定義View,沒有調用“native”層的方法),比如你的一個測試“demo”,可能不用配置什麼,只要啟動工具就好,混淆工作就這樣完成了。但是對於較復雜的工程,我們需要手動配置一些東西才能保證混淆過程正常完成,以及混淆之後的APK正常運行。那麼為什麼復雜的工程就一定要手動配置?究其根本原因就是:不是什麼東西都能被混淆的,有些代碼混淆之後,它所對應的功能就沒法使用。這主要體現在兩個地方: 1>所有第三方引用庫不能混淆,只要混淆,基本都是要出錯的。 2>諸如自定義“View”,“native”層的方法,等等都不能被混淆,混淆之後其對應的功能都要出錯,這類代碼比較多,我在這裡只列舉了兩個,後面我會詳細記錄一些。配置混淆文件就是為了告訴系統,某些東西不能混淆,以免我的APP會出錯,現在詳細介紹一下如何配置“ProGuard”混淆文件。  

兩個與混淆相關的配置文件

1>默認配置:工程剛創建的時候,開發環境其實已經默認配置好了混淆設置,你可以在“Eclipse路徑\sdk\tools\proguard\”路徑下面,找到一個“proguard-android.txt”文件,該文件是開發環境自動配置,混淆設置,這也是為什麼對於極簡單的項目而言,自己不用另外配置,系統默認配置就能滿足。對於默認配置這裡只做路徑介紹,接下來是重點問題,如何根據項目具體狀況,自行配置混淆文件。 2>自定義配置:在項目的根路徑下,有個文件:proguard-project.txt。該文件就是手動進行配置的地方。我們需要按照一定語法規則,根據項目實際狀況,編寫該文件,這樣才能正常通過混淆過程。  

自定義配置的具體規則

在“proguard-project.txt”文件裡面編寫混淆規則,其實只有一個目的:如果某個類混淆後,將會導致應用無法正常運行(或者部分功能無法正常運行)那麼就要在配置文件裡,聲明不要混淆這些類。我不關心為什麼這些類混淆之後會出錯,只關心哪些類在混淆後出錯,然後在文件裡面聲明不要去混淆這些類。當然如果你非得要刨根問底,你也可以在網上找更詳細地資料。

通用規則

以下,是通用的,幾乎所有工程都要避開的類(所謂避開,就是聲明這些類不要被混淆):

 

>四大組件以及系統基本的API不要混淆。

語法規則:-keep public class * extends xxxx

代碼示例:

 

#所有“Activity”及其子類不要混淆,同理,所有“Service”、“BroadcastReceiver”等等系統級別的類,不要混淆。
-keep public class * extendsandroid.app.Activity
-keep public class * extendsandroid.app.Application
-keep public class * extendsandroid.app.Service
-keep public class * extendsandroid.content.BroadcastReceiver
-keep public class * extendsandroid.content.ContentProvider
-keep public class * extendsandroid.app.backup.BackupAgentHelper
-keep public class * extendsandroid.preference.Preference
-keep public classcom.android.vending.licensing.ILicensingService

 

 

>保持”native”層的方法不要混淆。

 

-keepclasseswithmembernamesclass * {     
    native; 
}

 

 

>保持自定義控件,以及指定格式構造方法不要混淆。

 

-keepclasseswithmembers class * {
    public (android.content.Context, android.util.AttributeSet);  #保持自定義控件類不被混淆,指定格式的構造方法不去混淆
}

-keepclasseswithmembers class * {
    public (android.content.Context, android.util.AttributeSet, int);
}

 

 

>保持指定規則的方法不被混淆(Android layout 布局文件中為控件配置的onClick方法不能混淆)

 

-keepclassmembersclass * extends android.app.Activity {
    public void *(android.view.View);
}

 

>保持自定義控件指定規則的方法不被混淆

 

-keeppublic class * extends android.view.View { 
    public(android.content.Context);
    public(android.content.Context, android.util.AttributeSet);
    public(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
}


 

>所有枚舉類型不要混淆

 

-keepclassmembers enum * { 
   public static **[] values();
   public static ** valueOf(java.lang.String);
}

 

>需要序列化和反序列化的類不能被混淆(注:Java反射用到的類也不能被混淆)

 

#保持實現"Serializable"接口的類不被混淆
-keepnamesclass * implements java.io.Serializable
#保護實現接口Serializable的類中,指定規則的類成員不被混淆
-keepclassmembersclass * implements java.io.Serializable {
    static final long serialVersionUID;
    private static finaljava.io.ObjectStreamField[] serialPersistentFields;
    !static !transient ;
    private voidwriteObject(java.io.ObjectOutputStream);
    private voidreadObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

 

 

>保持實現"Parcelable"接口的類不被混淆

 

-keepclass * implements android.os.Parcelable { 
    public static finalandroid.os.Parcelable$Creator *;
}

 

 

>所有泛型不能混淆

 

-keepattributes Signature

 

>假如項目中有用到注解,應加入這行配置

 

-keepattributes *Annotation*

 

 

>保持R文件不被混淆,否則,你的反射是獲取不到資源id的

 

-keep class **.R$*{*;}

 

>保護WebView對HTML頁面的API不被混淆

 

-keep class **.Webview2JsInterface {*; }

 

>如果你的項目中用到了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.WebChromeClient {  
     public void *(android.webkit.WebView,java.lang.String);
}
至此,主要通用規則已經介紹完畢,你可以直接拷貝到“ProGuard”配置文件裡面去,這些規則都通用的。  

保持第三方引用庫(第三方Jar包)不被混淆

前文已經討論混淆通用規則,如果一個項目裡面,沒有引用第三方庫,基本上你只要將前面的規則根據情況給抄上去,混淆基本都不會有什麼問題。不過現在絕大多數項目(尤其是公司的項目)都會或多或少引入第三方庫,前文也已經有提到,所有第三方引用庫都不能夠混淆,所以接下來我們就聊聊,如何保持第三方庫不被混淆。

 

其實保持第三方庫不被混淆並不復雜,最關鍵的就是要細心+耐心。為什麼呢?保持第三方庫不被混淆,是要將你所引用的所有第三方庫,按照一定語法格式,寫在混淆配置文件裡面,簡單地說,就是將你"Eclipse"工程裡的"Android Private Libraries"目錄下面所有的第三方的引用包,按照給定語法格式,全部(注意是全部)寫到你的"proguard.project.txt"文件裡面。來讓我們看圖說話。

\

一圖勝千言,然後我們總結一下,對於每一個第三方的導入包,我們只要: >-libraryjars libs/xxxx.jar >-dontwarn 包名.** >-keep class 包名.** { *;} 大部分的第三方包都能按照這個規則配置,有些第三方引用包,在其官方網站上面會有混淆代碼配置說明,比如高德地圖就有,這個時候你抄上去就可以了。 好了現在讓我們把所有(對是所有)第三方包全都寫上,寫到手軟~  

運行程序,查漏補缺

一般來說,按照前面我們說討論的規則配置之後,混淆過程都能正常通過(不會報錯)。不過非常遺憾的是,混淆通過並不代表你APP就能運行,我們之前有提到過,有些東西原來不能混淆,當你混淆之後他的功能就會出錯。當你完成了混淆後,只要運行你APP,每個功能都按一按,多玩一下,就有可能發生一些奇怪的事。所以我們才需要做查漏補缺。 首先執行混淆之後,我們能夠在路徑"proguard"下面發現新出現了四個文件:

>mapping.txt:表示混淆前後代碼的對照表,這個文件非常重要。如果你的代碼混淆後會產生bug的話,log提示中是混淆後的代碼,希望定位到源代碼的話就可以根據mapping.txt反推。每次發布都要保留它方便該版本出現問題時調出日志進行排查,它可以根據版本號或是發布時間命名來保存或是放進代碼版本控制中。

>dump.txt:描述apk內所有class文件的內部結構。
>seeds.txt:列出了沒有被混淆的類和成員。
>usage.txt:列出了源代碼中被刪除在apk中不存在的代碼。

在我自己這個項目完成混淆代碼之後,發生兩件奇怪的事,現在說說怎麼利用這些文件進行解決。 >混淆過後的APP,所有列表(ListView)裡的數據,都不顯示,在確認了數據確實已經收到,就是沒有顯示之後,在usage.txt文件裡面,發現了所有"ListView"的適配器,也就是說,混淆過後的Apk,代碼裡面已經沒有適配器了,所以造成顯示失敗,而我當時所做的事,就是在混淆配置文件(proguard.project)文件裡面,添加了如下代碼:
#保持所有適配器類不被混淆,本應用中,不加這個將會導致適配器類加載失敗,所有列表項沒辦法顯示
-keep public class * extends android.widget.BaseAdapter
  不過這個不算是混淆的配置規則,因為我的另外一個同時,跟我類似項目結構,但是他沒有加這句,他的列表顯示正常。這是使用"usage.txt"文件進行查漏補缺的例子了。 >混淆過後的APP,越用越卡(其實就是內存洩露),用著用著手機就會莫名其妙的死機了,不單單是應用卡死,整台手機都不動了。不論在APP裡面進行什麼操作,都會導致APP將手機給弄死了。這個異常最終不是通過前面四個文件來解決的,而是通過對APP功能進行考慮。基於所發生的現象,可以看出,一定有什麼全局性東西,混淆之後發生錯誤,導致這個全局功能沒法運行,但是卻又不斷請求,最終耗盡系統資源。最終確定的原因是,我們APP的推送以及IM功能,混淆之後沒法工作,最終耗盡系統資源。那麼解決的辦法是,聲明他不要被混淆。  

總結

我們聊了那麼多的東西,先介紹了什麼叫做代碼混淆,然後介紹混淆方式選擇,接著介紹如何啟動以及配置混淆,最重要的當然就是配置混淆。其中包括通用規則,第三方包,以及根據混淆後的文件進行查漏補缺。至此,APP的代碼混淆基礎,介紹完畢。  
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved