Android 4.0 中由ProGuard引发的一场血案

转载 2012年11月07日 16:11:10

转自:http://blog.csdn.net/yihongyuelan/article/details/8129086


案件还原:

        修改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中注释以下代码:

[plain] view plaincopy
  1. define compile-java  
  2. $(hide) rm -f $@  
  3. $(hide) rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR)  
  4. $(hide) mkdir -p $(dir $@)  
  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.     $@ $(PRIVATE_JAR_MANIFEST) -C $(PRIVATE_CLASS_INTERMEDIATES_DIR) .  
  29. endef  
  30.   
  31. define transform-java-to-classes.jar  
  32. @echo "target Java: $(PRIVATE_MODULE) ($(PRIVATE_CLASS_INTERMEDIATES_DIR))"  
  33. $(call compile-java,$(TARGET_JAVAC),$(PRIVATE_BOOTCLASSPATH))  
  34. #Seven注释  
  35. #$(hide) rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR)  
  36. 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,代码如下:

[plain] view plaincopy
  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方法的,如:

[html] view plaincopy
  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引发的一场血案,

原文:http://blog.csdn.net/yihongyuelan/article/details/8129086 案件还原:         修改Android 4.0源码...
  • Nation_chen
  • Nation_chen
  • 2013年11月30日 13:27
  • 1171

Android 4.0 中由ProGuard引发的一场血案

案件还原:         修改Android 4.0源码中的Setting,添加一项功能之后,在eng模式下编译,一切正常,遂提交代码到服务器。第二天,传来噩耗,Setting上新添加的功能无法使...
  • gerryzhu
  • gerryzhu
  • 2014年03月18日 17:22
  • 489

merge_all引发的血案

merge_all引发的血案 在训练深度神经网络的时候,我们经常会使用Dropout,然而在test的时候,需要把dropout撤掉.为了应对这种问题,我们通常要建立两个模型,让他们共享变量。详...
  • w303096504
  • w303096504
  • 2017年10月15日 16:23
  • 235

《由〈一个馒头引发的血案〉引发的官司》

近日在网上无意中发现一部网络搞笑短篇《一个馒头引发的血案》,其大意是借用法制栏目的形式讲述电影《无极》的一些情节。看完后觉得还是蛮搞笑的,而后竟然发现网上惊献陈导要状告《馒头》的作者胡戈侵权,看完一些...
  • zergor
  • zergor
  • 2006年02月16日 13:58
  • 673

由Unity5.3.3f1引发的一场血案

昨天在公司发生一场由Unity5.3.3f1引起的血案,这件事让我深刻认识到Unity5.X到底有多坑啊! 事情的经过时这样的,我去客户公司拿IPad Air回公司进行测试和发布,我帮客户的IPad...
  • begonia__z
  • begonia__z
  • 2016年07月19日 11:23
  • 2257

由"@id/myid"引发的一场血案

今天反编译了某个应用,再一次的发现了,ids.xml和public.xml文件。之前有了解过相关的知识,但是仅限于一下某些“似是”的了解,这样挺危险的,故写下这段文字来做一个深入研究的第一步。 ...
  • love_android_2011
  • love_android_2011
  • 2013年01月07日 12:31
  • 820

一个馒头引发的血案...请看完无极后观看此片,保笑死人不偿命

一个馒头引发的血案...采用搞笑的手法拍摄的,笑到你喷饭,极大的讽刺无极下载地址:点击下载...
  • baggio785
  • baggio785
  • 2006年01月04日 13:08
  • 1009

转发同事总结:一个BUG引发的血案(起因篇)

序:博文《公司的一个“新记录”:项目初验不成功》记录了项目P初验不通过的事情,项目经理P在而后的一个星期顺利通过初验后,将以往的经验总结成文,在部门内部进行相互学习。应Stud_100等读者的要求,现...
  • waders
  • waders
  • 2008年12月17日 23:01
  • 587

一道试题引发的血案

某日,看到一道比较恶心的C语言的试题,考了很多比较绕的知识点,嘴脸如下:   int main(void) {     int a[4] = {1, 2, 3, 4}; ...
  • sanwu2010
  • sanwu2010
  • 2014年06月18日 18:42
  • 326

一个包子引发的血案?

2008年09月05日写过一篇日志,为的是告诉自己,其实,活着就记得:生活太美好了。地点:主楼   时间2010年 3月22日 外面刮着黄色的浮尘,莫非你的心也随着远方的尘土,再也不能安静了不成?难道...
  • watson243671
  • watson243671
  • 2010年03月22日 19:07
  • 605
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android 4.0 中由ProGuard引发的一场血案
举报原因:
原因补充:

(最多只允许输入30个字)