本文共 6203 字,大约阅读时间需要 20 分钟。
修改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进行编译。
打开源码中Settings文件夹下的Android.mk文件我们可以发现以下代码:
LOCAL_SRC_FILES := $(call all-java-files-under, src)
我们在这句代码后添加以下内容:
$(warning SevenTest LOCAL_SRC_FILES : $(LOCAL_SRC_FILES))
保存后终端中执行编译,我们可以看到src文件信息输出在终端上。复制到文件中查找lostClass.java,可以找到,因此可以推翻4的假设。
查看APK的编译打包流程,如图1:
根据以上流程,我们可以找到每一步对应的mk文件,这里我们找到AndroidSouceCode/build/core/definitons.mk中注释以下代码:
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是一个免费的java类文件压缩,优化,混淆器。它探测并删除没有使用的类,字段,方法和属性。它删除没有用的说明并使用字节码得到最大优化。它使用无意义的名字来重命名类,字段和方法。
1.创建紧凑的代码,快速装载和更小的内存占用。
2.增加程序和程序使用的库被反编译的难度。
3. 删除来自源文件中的没有调用的代码 。在大致了解了ProGuard的作用之后,回过头来看看lostClass.java,该类主要在Settings_heads.xml中调用,并没有通过其他方式调用,难道这被ProGuard误认为是没有使用的代码而给优化掉了?
打开源码Settings下的Android.mk文件,可以看到以下代码:
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
继续查看当前路径下的proguard.flags,代码如下:
-keep class com.android.settings.xxxx.*
这里的xxxx表示lostClass.java的包名。
再次进入终端,切换到usr模式下编译代码,测试成功,但有几个小的功能还是不能正常使用。通过以上至少证明假设三是正确的,即在ProGuard过程中产生的问题。
这几个不能使用的小功能,都有一个共同的特点,即点击Button后报错,提示找不到对应的方法。这几个Button是在xml文件中添加onClick方法的,如:
-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还有一个可以实现对Android应用的优化与保护,不过这货是收费的(最便宜都要350 € 欧元啊。。。换算过来要将近3000快RMB。。。o(╯□╰)o)。
最后在大人的明察秋毫下,真相终于大白。
转载地址:http://iolli.baihongyu.com/