Android有的时候有分DEX的需求,当方法数超过了66535这个数,我们就需要开启MultiDex,还有的时候我们有的部分需求是一直不会改变,那这个时候我们可以将对应的代码单独打包成DEX,可以预先放置到Assert目录下, 在需要的时候进行加载
热更新也有这样的需求,将出问题的代码单独打入一个path dex中,我们知道在加载dex到内存中时,如果不存在odex文件,那么就会先执行dexOpt,代码中会执行dvmVerifyClass,这个方法主要是为了防止类被篡改,改变了类的合法性,如果当前的类和引用的类都在同一个dex时,会被打上CLASS_ISVERIFYED标志,这个如果有问题,path中dex肯定与调用代码不在同一个dex,这时就会出问题,很多解决该问题的方案都是类都引用一个其他dex中的类, 防止被打上该标记。
生成DEX有很多方式,比如官方的Multi dex,可以指定那些类被打入到main dex中,其他的类打入其他的dex中,那我们这里主要是采用命令行的方式将指定的类打入指定的dex中,
将代码打入dex主要有以下步骤:
比如我们写了下面的一个java代码:
package com.demo.bean;
/**
* Created by xxx on 2017/7/20.
*/
public class DexDemo {
public String call() {
return "dex demo";
}
}
这里我们可以直接用javac命令来编译,也可以采用Android Studio build一次生成,build 后可以可以在如下路径找到class文件:
我们可以在build目录下找到对应的calss。也可以采用如下命令,在cmd或者mac的terminal中进入对应java的目录输入:
:JavaTest doc$ javac DexDemo.java
我们将编译后的class成jar包,生成jar包的方式也有很多中,比如网上有:可能是最通用全面的Android studio打包jar方法,这里我们采用命令行的方式进行,这里生成的时候要注意一下不同的生成方式会出现在dex过程时会出现不同的错误,以下就一种一种的演示:
在命令行输入如下的命令:
jar cvf dexdemo1.jar /Users/doc/JavaTest/DexDemo.class
这时我们会生成dexdemo1的jar包,这里主要是为了演示不同的class来生成jar后dex的不同。
命令同1:
jar cvf dexdemo2.jar /Users/doc/Android/Demo/app/build/intermediates/classes/debug/com/demo/bean/DexDemo.class
只是路径变化了,其他的都没有变化
可以发现两个jar的大小不一样,同样的代码生成的居然大小不一样,后面就会知道为什么?
生成了jar后,我们就可以采用命令来生成对应的dex了,我们采用dx命令来生成,这里命令主要在Android sdk下bulid-tools中的dx工具。
输入如下的命令:
dx --dex --output dex1.dex dexdemo1.jar
前面dex1.dex是生成的dex文件,后面是对应的jar,我还是在之前生成的jar路径下操作的,如果你发现dx不可用,可以加入到环境变量。
执行命令后成功的生成了如下错误:
UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.RuntimeException: Exception parsing classes
at com.android.dx.command.dexer.Main.processClass(Main.java:752)
at com.android.dx.command.dexer.Main.processFileBytes(Main.java:718)
at com.android.dx.command.dexer.Main.access$1200(Main.java:85)
at com.android.dx.command.dexer.Main$FileBytesConsumer.processFileBytes(Main.java:1645)
at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)
at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)
at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
at com.android.dx.command.dexer.Main.processOne(Main.java:672)
at com.android.dx.command.dexer.Main.processAllFiles(Main.java:574)
at com.android.dx.command.dexer.Main.runMonoDex(Main.java:311)
at com.android.dx.command.dexer.Main.run(Main.java:277)
at com.android.dx.command.dexer.Main.main(Main.java:245)
at com.android.dx.command.Main.main(Main.java:106)
Caused by: com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)
at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)
at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)
at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)
at com.android.dx.command.dexer.Main.parseClass(Main.java:764)
at com.android.dx.command.dexer.Main.access$1500(Main.java:85)
at com.android.dx.command.dexer.Main$ClassParserTask.call(Main.java:1684)
at com.android.dx.command.dexer.Main.processClass(Main.java:749)
... 12 more
1 error; aborting
这里出现的错误主要是java编译版本不对,这里我们查看一下java版本:
xxMacBook-Pro:JavaTest doc$ java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
改成1.7可以解决这个问题。
输入的命令同上,只是输入源改变了:
dx --dex --output dex2.dex dexdemo2.jar
执行命令后,成功的生成了如下错误:
UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.RuntimeException: Exception parsing classes
at com.android.dx.command.dexer.Main.processClass(Main.java:752)
at com.android.dx.command.dexer.Main.processFileBytes(Main.java:718)
at com.android.dx.command.dexer.Main.access$1200(Main.java:85)
at com.android.dx.command.dexer.Main$FileBytesConsumer.processFileBytes(Main.java:1645)
at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)
at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)
at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
at com.android.dx.command.dexer.Main.processOne(Main.java:672)
at com.android.dx.command.dexer.Main.processAllFiles(Main.java:574)
at com.android.dx.command.dexer.Main.runMonoDex(Main.java:311)
at com.android.dx.command.dexer.Main.run(Main.java:277)
at com.android.dx.command.dexer.Main.main(Main.java:245)
at com.android.dx.command.Main.main(Main.java:106)
Caused by: com.android.dx.cf.iface.ParseException: class name (com/demo/bean/DexDemo) does not match path (Users/doc/Android/Demo/app/build/intermediates/classes/debug/com/demo/bean/DexDemo.class)
at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:520)
at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)
at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)
at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)
at com.android.dx.command.dexer.Main.parseClass(Main.java:764)
at com.android.dx.command.dexer.Main.access$1500(Main.java:85)
at com.android.dx.command.dexer.Main$ClassParserTask.call(Main.java:1684)
at com.android.dx.command.dexer.Main.processClass(Main.java:749)
... 12 more
1 error; aborting
这次不是版本不对了,而是类不匹配了,我们发现匹配的规则是将整改路径都加入了Users/doc/Android/Demo/app/build/intermediates/classes/debug/com/demo/bean/DexDemo.class
这也是为什么第二次生成的jar比第一次的大,那这里看来我们需要重新生成jar了,前面的路径都不要了,只需要从包名开始就可以了。
这里我们将debug下com包拷贝到我们的JavaTest目录下,重新输入生成jar的命令:
jar cvf dexdemo3.jar com/demo/bean/DexDemo.class
我们重新生成了jar3,我们再次运行dx命令生成dex:
dx --dex --output dex3.dex dexdemo3.jar
这里主要是版本错误的疑问,我javac用的1.8的版本,我Android Studio用的也是1.8版本,我们可以从project structure里面看我们用的jdk, 从/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home
可以看到jdk版本:
JAVA_VERSION="1.8.0_112"
OS_NAME="Darwin"
OS_VERSION="11.2"
OS_ARCH="x86_64"
SOURCE=""
发现两次都用的1.8,但是javac的dx执行的时候就会出现版本错误。
上面我们生成了dex,但是还没有验证dex是否正确,这里我们可以用apktool来反解,将dex复制到dex2jar目录下,然后执行命令:sh dex2jar.sh dex3.dex 执行成功后会生成一个dex3_dex2jar.jar文件:
这里我们用jd_GUI来查看一下:
其实上面的都可以用gradle来进行,开发中也是采用gradle更为方便, 这里只是为了实现一下用命令行来生成dex。