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

來源:互聯網
上載者:User

標籤:

謹以此文,記我在公司實習時,所接到的第一個正式的、真正有意義的任務——將公司即將發布的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”簡介,如果你想看詳細的,這裡附上官網地址:http://proguard.sourceforge.net/

“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<methods>; }


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

-keepclasseswithmembers class * {    public <init>(android.content.Context, android.util.AttributeSet);  #保持自訂控制項類不被混淆,指定格式的構造方法不去混淆}-keepclasseswithmembers class * {    public <init>(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<init>(android.content.Context);    public<init>(android.content.Context, android.util.AttributeSet);    public<init>(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 <fields>;    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的代碼混淆基礎,介紹完畢。

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.