1. 程式人生 > >Android 4.0 中由ProGuard引發的一場血案

Android 4.0 中由ProGuard引發的一場血案

案件還原:

        修改Android 4.0原始碼中的Setting,新增一項功能之後,在eng模式下編譯,一切正常,遂提交程式碼到伺服器。第二天,傳來噩耗,Setting上新新增的功能無法使用,一點選則報錯。

案件分析:

        上傳程式碼之前,已經在本地編譯測試過,咋會有錯呢??!!管它三七二十一,操起adb logcat抓取log進行分析。不看不知道,一看嚇一跳,log中顯示的錯誤資訊竟然是ClassNotFound,也就是找不到相應的類,難道我上傳的時候遺漏了嗎(有這種可能)?馬上檢視提交記錄,逐個檢查提交的檔案,全部提交了啊!咋會出現ClassNotFound呢?這時候咋辦呢?冷靜下來分析後可以知道,本地測試一般是在eng模式下編譯的,而這次報錯的是伺服器在usr模式下編譯處出來的。難道eng編譯和usr編譯出來的結果不一致??!!

案件進一步分析:

        在本地選擇usr編譯模式,編譯程式碼進行測試,果然出現了與伺服器一樣的錯誤,即ClassNotFound。

        1.既然eng模式編譯的程式碼能夠正常執行,證明程式碼邏輯以及功能都是OK的。

        2.usr編譯出來出現ClassNotFound的問題,證明usr和eng編譯模式不同導致編譯結果差異。

        3.比較eng模式和usr模式編譯出來的Settings.apk,發現大小竟然不一致,用apktool反解後對比發現,usr模式下編譯的Settings.apk中的確少了一個類(後文用lostClass.java代替)。

案件調查:

假設一:

        難道是編譯過程中沒有將lostClass.java編譯進去?

驗證一:

        檢視編譯過程中是否有對lostClass.java進行編譯。

1.第一種檢視方法。

        開啟原始碼中Settings資料夾下的Android.mk檔案我們可以發現以下程式碼:

        LOCAL_SRC_FILES := $(call all-java-files-under, src)

        我們在這句程式碼後新增以下內容:

        $(warning SevenTest LOCAL_SRC_FILES : $(LOCAL_SRC_FILES))

        儲存後終端中執行編譯,我們可以看到src檔案資訊輸出在終端上。複製到檔案中查詢lostClass.java,可以找到,因此可以推翻4的假設。

2.第二種檢視方法。

        檢視APK的編譯打包流程,如圖1:


        根據以上流程,我們可以找到每一步對應的mk檔案,這裡我們找到AndroidSouceCode/build/core/definitons.mk中註釋以下程式碼:

  1. define compile-java  
  2. $(hide) rm -f [email protected]  
  3. $(hide) rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR)  
  4. $(hide) mkdir -p $(dir [email protected])  
  5. $(hide) mkdir -p $(PRIVATE_CLASS_INTERMEDIATES_DIR)  
  6. $(call unzip-jar-files,$(PRIVATE_STATIC_JAVA_LIBRARIES),$(PRIVATE_CLASS_INTERMEDIATES_DIR))  
  7. $(call dump-words-to-file,$(PRIVATE_JAVA_SOURCES),$(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list)  
  8. $(hide) if [ -d "$(PRIVATE_SOURCE_INTERMEDIATES_DIR)" ]; then \  
  9.         find $(PRIVATE_SOURCE_INTERMEDIATES_DIR) -name '*.java' >> $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list; \  
  10. fi  
  11. $(hide) tr ' ' '\n' < $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list \  
  12.     | sort -u > $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq  
  13. $(hide) $(1) -encoding UTF-8 \  
  14.     $(strip $(PRIVATE_JAVAC_DEBUG_FLAGS)) \  
  15.     $(if $(findstring true,$(LOCAL_WARNINGS_ENABLE)),$(xlint_unchecked),) \  
  16.     $(2) \  
  17.     $(addprefix -classpath ,$(strip \  
  18.         $(call normalize-path-list,$(PRIVATE_ALL_JAVA_LIBRARIES)))) \  
  19.     $(if $(findstring true,$(LOCAL_WARNINGS_ENABLE)),$(xlint_unchecked),) \  
  20.     -extdirs "" -d $(PRIVATE_CLASS_INTERMEDIATES_DIR) \  
  21.     $(PRIVATE_JAVACFLAGS) \  
  22.     \@$(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq \  
  23.     || ( rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR) ; exit 41 )  
  24. #Seven註釋  
  25. #$(hide) rm -f $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list  
  26. #$(hide) rm -f $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq  
  27. $(hide) jar $(if $(strip $(PRIVATE_JAR_MANIFEST)),-cfm,-cf) \  
  28.     [email protected] $(PRIVATE_JAR_MANIFEST) -C $(PRIVATE_CLASS_INTERMEDIATES_DIR) .  
  29. endef  
  30. define transform-java-to-classes.jar  
  31. @echo "target Java: $(PRIVATE_MODULE) ($(PRIVATE_CLASS_INTERMEDIATES_DIR))"  
  32. $(call compile-java,$(TARGET_JAVAC),$(PRIVATE_BOOTCLASSPATH))  
  33. #Seven註釋  
  34. #$(hide) rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR)  
  35. endef  

        這樣我們在終端中執行編譯命令後,可以在路徑:AndroidSourceCode/out/target/common/obj/APPS/Settings_intermediates/classes找到以下兩個檔案:

        java-source-list和java-source-list-uniq,在其中搜索lostClass.java即可以找到。

        通過以上檢視可以知道,lostClass.java在是被編譯了的,最後為了驗證,我們可以通過jd-gui這個工具來檢視AndroidSourceCode/out/target/common/obj/APPS/Settings_intermediates/classes.jar,通過檢視該jar檔案中包含了lostClass.java,從而假設一排除。

假設二:

        Settings.apk在打包的時候出現異常。

驗證二:

        對比eng模式以及usr模式下編譯的輸出資訊後發現,二者copy的dex檔案不同。如下:

usr模式:

Copying: out/target/common/obj/APPS/Settings_intermediates/proguard.classes.dex

eng模式:

Copying: out/target/common/obj/APPS/Settings_intermediates/noproguard.classes.dex

        通過比較可以知道:proguard.classes.dex比noproguard.classes.dex小,這裡的proguard就是混淆。也就是說usr模式對程式碼進行了混淆,從而使得usr模式編譯出的Settings.apk與eng模式編譯出的大小不同。是否會是usr模式對程式碼進行ProGuard導致問題的呢?這裡暫時不知道假設二正確與否。

假設三:

        usr模式下編譯系統對程式碼進行ProGuard時出現異常。

驗證三:

        先了解一下ProGuard這個工具;

ProGuard簡介:

        ProGuard是一個免費的java類檔案壓縮,優化,混淆器。它探測並刪除沒有使用的類,欄位,方法和屬性。它刪除沒有用的說明並使用位元組碼得到最大優化。它使用無意義的名字來重新命名類,欄位和方法。 

ProGuard作用:

        1.建立緊湊的程式碼,快速裝載和更小的記憶體佔用。

        2.增加程式和程式使用的庫被反編譯的難度。

        3.刪除來自原始檔中的沒有呼叫的程式碼 。

        在大致瞭解了ProGuard的作用之後,回過頭來看看lostClass.java,該類主要在Settings_heads.xml中呼叫,並沒有通過其他方式呼叫,難道這被ProGuard誤認為是沒有使用的程式碼而給優化掉了?

        開啟原始碼Settings下的Android.mk檔案,可以看到以下程式碼:

        LOCAL_PROGUARD_FLAG_FILES := proguard.flags

        繼續檢視當前路徑下的proguard.flags,程式碼如下:

  1. # Keep all Fragments in this package, which are used by reflection.  
  2. -keep class com.android.settings.*Fragment  
  3. -keep class com.android.settings.*Picker  
  4. -keep class com.android.settings.*Settings  
  5. -keep class com.android.settings.wifi.*Settings  
  6. -keep class com.android.settings.deviceinfo.*  
  7. -keep class com.android.settings.bluetooth.*  
  8. -keep class com.android.settings.applications.*  
  9. -keep class com.android.settings.inputmethod.*  
  10. -keep class com.android.settings.MasterClear  
  11. -keep class com.android.settings.MasterClearConfirm  
  12. -keep class com.android.settings.accounts.*  
  13. -keep class com.android.settings.fuelgauge.*  

        稍稍檢視ProGuard的語法後知道,這樣表示哪些類不會完全被ProGuard處理。先在這裡加上一句程式碼:

        -keep class com.android.settings.xxxx.*

        這裡的xxxx表示lostClass.java的包名。

        再次進入終端,切換到usr模式下編譯程式碼,測試成功,但有幾個小的功能還是不能正常使用。通過以上至少證明假設三是正確的,即在ProGuard過程中產生的問題。

        這幾個不能使用的小功能,都有一個共同的特點,即點選Button後報錯,提示找不到對應的方法。這幾個Button是在xml檔案中新增onClick方法的,如:

  1. <Button
  2.     android:id="@+id/JumpButton"
  3.     android:onClick="JumpFuction"
  4.     android:layout_width="0dip"
  5.     android:layout_height="wrap_content"
  6.     android:layout_weight="1"/>
        通過檢視ProGuard官網的Example——A complete Android Application可以知道,還需要在proguard.flags中新增以下程式碼:
-keepclassmembers class * extends android.content.Context {
   public void *(android.view.View);
   public void *(android.view.MenuItem);
}
        官網給出的解釋是:We're also keeping possible onClick handlers in custom Context extensions, since they might be referenced from XML layout files.

        也就是說:有的onClick事件是在xml中定義的,所以在proguard.flags檔案中新增以上內容從而保證這些onClick觸發方法不會被ProGuard優化掉。加上以上程式碼,編譯,測試,通過!!

案件回顧:

        整個案件看似撲朔迷離,但背後隱藏的是APK整個編譯流程。一開始看似不可能的事情(usr模式和eng模式編譯結果不一致),經過小白一步一步的跟蹤後竟然發現了背後的“驚天祕密”。ProGuard是一個免費的開源專案,用以壓縮、優化、混淆程式碼,Android 原始碼集成了proguard,預設會在usr以及usrdebug模式下開啟,這一點可以在AndroidSouceCode4.0/build/core/package.mk中找到:

ifndef LOCAL_PROGUARD_ENABLED
ifneq ($(filter user userdebug, $(TARGET_BUILD_VARIANT)),)
    # turn on Proguard by default for user & userdebug build
    LOCAL_PROGUARD_ENABLED :=full
endif
endif

        (PS:高通的原始碼與google原生的差不多,而MTK則註釋掉了這裡的LOCAL_PROGUARD_ENABLED :=full,即usr、usrdebug、eng模式編譯出來的結果都沒經過混淆。)

當然除了ProGuard還有一個DexGuard可以實現對Android應用的優化與保護,不過這貨是收費的(最便宜都要350 € 歐元啊。。。換算過來要將近3000快RMB。。。o(╯□╰)o)。

        最後在大人的明察秋毫下,真相終於大白。

相關推薦

Android 4.0 ProGuard引發血案

案件還原:         修改Android 4.0原始碼中的Setting,新增一項功能之後,在eng模式下編譯,一切正常,遂提交程式碼到伺服器。第二天,傳來噩耗,Setting上新新增的功能無法使用,一點選則報錯。 案件分析:         上傳程式碼之前,已經在本地編譯測試過,咋會有錯呢??!!管

Android 4.0去掉標題欄和狀態列的方法

    <style name="Theme.Holo.NoTitleBar">         <itemname="android:windowFullscreen">false</item>           <itemname="android:windo

.net 4.0 對多執行緒新特性(

      在.net 40中對多執行緒的處理增加了很多新的類以方便多執行緒環境下的程式設計實現,首先需要了解的是兩個非常有用的類Lazy<T>和ThreadLazy<T>,通過這兩個類我們可以很方便實現一個單例模式而不用考慮太多的執行緒安全的問題。

【轉】Android 4.0 Launcher2源碼分析——啟動過程分析

handler flag 這一 第一次啟動 asynctask pla size ontouch wait Android的應用程序的入口定義在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="htt

Android 4.0 Launcher2源碼分析——主布局文件(轉)

not sch png 默認 顯示效果 target ots eat col 本文來自http://blog.csdn.net/chenshaoyang0011 Android系統的一大特色是它擁有的桌面通知系統,不同於IOS的桌面管理,Android有一個桌面系統用於管

Flink1.4.0反序列化及序列化類變化

繼承 tde post 變化 flink ted 標記 api col Flink1.4.0中,反序列化及序列化時繼承的類,有一些被標記為了“@deprecated”,路徑上也有變化: 1.AbstractDeserializationSchema 以前路徑 org

Unity 4.0 的新動畫系統——MecAnim

alt clas 講解 unit 組件 之一 new src align   分享一個文檔資料,關於動畫系統的,版本應該很老了,但是有借鑒意義的;     Unity 4.0 已於 2012 年 11 月 15 日正式發布,Unity 每一次版本的提升,都給遊戲開發者帶來

如何解決Android 5.0出現的警告:Service Intent must be explicit

有些時候我們使用Service的時需要採用隱私啟動的方式,但是Android 5.0一出來後,其中有個特性就是 Service Intent  must be explitict ,也就是說從Lollipop開始,service服務必須採用顯示方式啟動。

Android 5 0使用JobScheduler

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

android 4 0 以上平臺選擇圖片報錯Attempted to access a cursor after it

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android 6.0 新的新技術

七、低功耗藍芽掃描優化   優化了低功耗藍芽掃描優化的掃描。現在低功耗藍芽的應用越來越多,很多 APP 都需要掃描裝置,掃描裝置是一個非常重的操作,希望這次改動,能夠帶來一些改善。   八、支援主題化的 ColorStateLists   使用

Android 4.0新控制元件 switch的屬性

<style name="widget_gender_switch">         <item name="android:layout_height">wrap_content</item>  

藍芽4.0 BLE 資料傳輸 (

原文地址:http://blog.sina.com.cn/s/blog_869234dc0102uxl3.html   在這之前我們得先了解一下一些專業詞彙: 1、profile  profile可以理解為一種規範,一個標準的通訊協議,它存在於從機中。 藍芽組織規

Python(4)_Python的資料型別

     一   浮點型定義的兩種種方式 # 宣告一個浮點型的第一種方式,變數常用的三個操作 # 獲取值 變數名 # 獲取型別 type # 獲取id號,下面這個定義是指向同一個記憶體空間的,所以打印出來的id號一樣的!

Android 6.0SELinux的TE簡介

在開發中,偶爾會碰到一些TE字尾的檔案的修改和檢視。google借鑑了SELinux安全機制,在Android內包含了該機制,而TE是SELinux中描述程式訪問資源的語言。本文的目的是讓大家在Android開發中,碰到相關問題時能夠看懂相關的TE檔案。在下面的內容中將描述SEL

Android 4.0 徹底說再見!

新人笑,舊人哭。好吃的“冰淇淋三明治”融化了,而我們也是時候與 Android 4.0 告別了! 在 Google 最新公佈的一份截止 2018 年 10 月 26 日,Android 系統各版本所佔市場份額的統計表中可以看到,當前全球第一大作業系統 And

Android 4 0 事件輸入 Event Input 系統

                 1. TouchScreen功能在Android4.0下不工作       原來在Android2.3.5下能正常工作的TouchScreen功能,移植到Android 4.0就不能正常工作了。憑直覺,Android4.0肯定有鬼。真是不看不知道,一看嚇一跳。在Android

.NET 4.0 的契約式程式設計

契約式程式設計不是一門嶄新的程式設計方法論。C/C++ 時代早已有之。Microsoft 在 .NET 4.0 中正式引入契約式程式設計庫。博主以為契約式程式設計是一種相當不錯的程式設計思想,每一個開發人員都應該掌握。它不但可以使開發人員的思維更清晰,而且對於提高程式效能很有幫助。值得一提的是,它對於並行程式

Android 4.0 ICS SystemUI淺析——SystemUI啟動流程

閱讀Android 4.0原始碼也有一段時間了,這次是針對SystemUI的一個學習過程。本文只是對SystemUI分析的一個開始——啟動流程的分析,網上有很多關於2.3的SystemUI的分析,可4.0與2.3的差別還是很大的,為了給自己留下筆記同時也方便大家學習和探討,遂寫此文,後續將有更多關

學習使用ExpressJS 4.0的新Router

概述 ExpressJS 4.0中提出了新的路由Router。Router好比是一個“迷你版”的express應用,它沒有引入views或者settings,但是提供了路由應有的API,.use,.get,.param和route。 示例應用 讓我們建立一個express應用,僅僅有少量routes和功