Android中的 Multiple dex files define 编译错误引发的思考
昨天我龙哥问我一个问题。他说假设一个project中,有一个com.x.A枚举。导入的第三方jar中也有一个com.x.A枚举,那么我在project中用A枚举的时候,会用到那个枚举呢?我当时一想,这个不是类(枚举是个特殊类)定义冲突吗?应该在编译的时候就报错呢,并且这个问题我之前遇到过,所以我非常自信的和他说,这个应该在编译的时候就报错,结果他来了一句:没有呀?执行成功了,并且导入的是project中的那个枚举A,我擦,我一想这不是打我脸吗?我记得非常清楚,是会报错的呀,所以我就自己写了一个AndroidDemoproject:
project非常easy,那么执行一下:
妈蛋,果然报错呀。编译就失败了,所以我又非常有底气的去找他理论,说我这都能够的呀,然后他也说没有问题,那么问题就陷入僵局了,可是我不能这么算了呀,我就怀疑是环境的问题?project的问题?编译器的问题?一顿怀疑之后,那就尝试。首先,推測我们两的调用方式是否一样,他使用maven的,而我用的是build paths方式的,这个有点不同。所以我就叫他改成我这样的,同一时候他使用注解的方式用NodeCode的。我是用NodeCode a = NodeCode.A方式。然后叫我哥把使用方法改成和我一模一样的,编译一下之后,他还是没有报错?那么是不是环境问题呢?他用的是idea,我用的是Eclipse,然后我用idea工具试了一下,也是报错的,那么不是编辑器的问题。蛋疼了一会之后,细致看看那个错误。发现是dex字眼。突然想到。我哥是开发Web的,我是开发Android的,他的project是Webproject,我是Androidproject,这是不是问题呢?所以我立刻新建一个Javaproject来測试一下(事实上Javaproject和JavaWebproject都一样。由于都是用JVM虚拟机的。用javac进行编译的):
执行:
尼玛。尽然能够。擦,果然是project问题。并且。我们在project中的NodeCode中信息打印
发现打印的信息,能够得知,默认优先导入的是project中的那个枚举类。
那么问题弄清楚了,Android中项目不能够,Java项目能够
可是我们得看看为什么呢?所以仅仅能通过源代码去看问题了,可是在看源代码之前。事实上我们能够推測一下问题的根源?
Android中的是dex文件,我们知道dex文件是用dx命令将多个class文件合成得到的,所以dex是一个文件,他有详细的格式,关于dex的格式说明,不了解的同学能够查看这篇文章:http://www.wjdiankong.cn:8888/blog/?
p=508,而我们知道一个jar或者是执行的Javaproject,都是编译之后在bin文件夹下的class文件,我们推測编译时都报错了。那么肯定是发生在dx执行的那一块,所以我去找dx命令的源代码,源代码位置:源代码文件夹\dalvik\dx\src\com\android\dx\command
主要看main方法:
我们再到这个类看看:com.android.dx.command.dexer.Main
入口方法main:
这里解析命令行參数。然后执行run方法:
我们看到这里有一个非常重要的方法。就是合并引用第三方的jar的dex内容,正是我们想要知道的结果,进入看看:
方法的凝视:
/**
* Merges the dex files in library jars. If multiple dex files define the
* same type, this fails with an exception.
*/
到这里,事实上我们看到这种方法的凝视说明。就知道了假设定义了同样的types就会抛出一个异常,我们在进入看看什么类型,抛出来的异常和我们看到的是一样吗?
有一个重要的类:DexMerger 位于:com.android.dx.merge.DexMerger
这个类的构造方法会传递两个DexBuffer进去。第一个DexBuffer是我们之前一定操作好的dex内容,第二个DexBuffer是我们须要合并的Library的dex内容,构造好类之后。在调用merge方法:
merge方法中调用了mergeDexBuffers方法:
我擦。看到这里是不是有点熟悉,merge非常多方法,并且这些merge的动作就是我们之前解析dex文件格式中说道的那几种类型,可是这里我们是类定义冲突。那么肯定是看mergeClassDefs方法:
在这种方法中会先得到有序的type类型,然后在设置classDefs区域的偏移值和大小,在看看getSortedTypes方法:
这里事实上是将dexA和dexB中的typeIds进行排序合并,在看看readSortableTypes方法:
好吧。最终看到核心的内容了。在这里会推断这个type是否已经存在了,假设存在的话,就会抛出异常信息,并且这个异常信息就是和我们之前看到的一样:
到这里我们能够知道。dex文件里是不同意有同样的类(包名+类名),并且这里的包名+类名就是type。这个在之前解析dex文件格式中有说道,这里就不解释了。
事实上我们能够想想,dex是一个文件,他有自己的文件结构。同样的内容是不可能存在的,会出现冲突。
所以如今我们知道为何Androidproject不行。原因就是dx在进行class到dex转化的时候就出错了。也就是发生在编译期。
可是在Javaproject中是能够的,事实上想一下还是合理的,由于假设我们是将一个Javaproject变成一个Jar文件,事实上jar文件事实上是一个压缩包。压缩包里面是不会存在同样的文件的(包名会转化成指定的文件文件夹)。假设有的话,也不会出现冲突。不会出现故障的。可是我们从上面的样例能够看到。会优先导入本project中的类,所以这个能够查看一下jar工具的源代码。地址:
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/tools/jar/Main.java
看这里的main方法,事实上他内部用了ZipFile/ZipEntry/ZipInputStream这三个类。来组件一个jar的,操作也非常easy,首先得到全部的class文件,然后将包名转化成文件文件夹就可以。可是这里并没有找到假设有同样的第三方jar包括同样的类,会不会进行覆盖的代码的地方,所以这一步靠大家了。可是从上面的样例能够看到,默认用的是project中的那个类。
这里在多说一句吧,就是我们在导出一个jar的时候,假设要携带第三方的jar的话。导出来我所知道的是两个方法:
1、使用Eclipse插件:fat_jar
2、使用ant脚本跑出来一个
关于网上另一个方法就是用Eclipse自带的Export,可是须要自己写一个MANIFEST.MF文件。只是这样的方法我没成功过。不知道行不行。只是导出来携带第三方的jar的jar,应该是这样的样式:
事实上,我们能够看出来上面的那两种方式的原理:
首先将本project中的class文件变成jar,然后在操作libs下的第三方的jar内容,那么以下就是相当于将多个jar进行合并的操作,事实上这个就非常easy了,我们自己都能够写一个程序。使用ZipFile+ZipEntry就可以,假设发现有相相应的ZipEntry的话,就跳过就可以,那么最终就能够合并多个jar了。当然这个仅仅是我们的猜想,可是从上面的那个样例能够看到,Javaproject能够导入多个包括同样类的jar,不会发生冲突,可是Androidproject不行。原因是dx将多个class转化成dex时会进行推断。
须要思考的问题:
当遇到这个问题的第一时间应该想到是编译器出现的问题,所以这个和Android中的DVM和Java中的JVM没有关系。由于虚拟机是在执行期才会用到。所以我们应该从Android的编译过程去看问题。这是解决这个问题的思路问题。
得到的知识点:Androidproject中是不同意存在同样的类。Javaproject是能够的
很多其它内容:点击这里
关注微信公众号,最新Android技术实时推送