命令行指定的类打入单独的DEX
Posted freeCodeSunny
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了命令行指定的类打入单独的DEX相关的知识,希望对你有一定的参考价值。
需求
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代码
- 编译成class
- 打包成jar
- dx命令生成对应的dex
编写java代码
比如我们写了下面的一个java代码:
package com.demo.bean;
/**
* Created by xxx on 2017/7/20.
*/
public class DexDemo
public String call()
return "dex demo";
class生成
这里我们可以直接用javac命令来编译,也可以采用Android Studio build一次生成,build 后可以可以在如下路径找到class文件:
我们可以在build目录下找到对应的calss。也可以采用如下命令,在cmd或者mac的terminal中进入对应java的目录输入:
:JavaTest doc$ javac DexDemo.java
生成jar
我们将编译后的class成jar包,生成jar包的方式也有很多中,比如网上有:可能是最通用全面的Android studio打包jar方法,这里我们采用命令行的方式进行,这里生成的时候要注意一下不同的生成方式会出现在dex过程时会出现不同的错误,以下就一种一种的演示:
1:命令行的class生成jar
在命令行输入如下的命令:
jar cvf dexdemo1.jar /Users/doc/JavaTest/DexDemo.class
这时我们会生成dexdemo1的jar包,这里主要是为了演示不同的class来生成jar后dex的不同。
2:build class生成jar
命令同1:
jar cvf dexdemo2.jar /Users/doc/Android/Demo/app/build/intermediates/classes/debug/com/demo/bean/DexDemo.class
只是路径变化了,其他的都没有变化
经过上述两个步骤,我们生成了两个jar:
可以发现两个jar的大小不一样,同样的代码生成的居然大小不一样,后面就会知道为什么?
生成dex
生成了jar后,我们就可以采用命令来生成对应的dex了,我们采用dx命令来生成,这里命令主要在Android sdk下bulid-tools中的dx工具。
jar1生成
输入如下的命令:
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可以解决这个问题。
jar2生成
输入的命令同上,只是输入源改变了:
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了,前面的路径都不要了,只需要从包名开始就可以了。
重新生成jar3
这里我们将debug下com包拷贝到我们的JavaTest目录下,重新输入生成jar的命令:
jar cvf dexdemo3.jar com/demo/bean/DexDemo.class
我们重新生成了jar3,我们再次运行dx命令生成dex:
dx --dex --output dex3.dex dexdemo3.jar
运行命令后,你就可以看到本地生成了一个dex:
疑问
这里主要是版本错误的疑问,我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。
以上是关于命令行指定的类打入单独的DEX的主要内容,如果未能解决你的问题,请参考以下文章
Android 无法在 main-dex 文件中放置请求的类
Android Gradle 插件自定义 Gradle 任务 ② ( 在 Terminal 面板中执行 gradlew task 命令显示所有任务 | 命令行输出所有任务 | 单独执行指定任务 )
Android Gradle 插件自定义 Gradle 任务 ② ( 在 Terminal 面板中执行 gradlew task 命令显示所有任务 | 命令行输出所有任务 | 单独执行指定任务 )
错误记录记录 Android 命令行执行 Java 程序中出现的错误 ( dx 打包 PC 可执行文件报错 | dalvik 命令执行 kotlin 编译的 dex 文件报错 )
开发环境Android 命令行中执行 Java 程序 ( IntelliJ IDEA 中创建 Java / Kotlin 工程 | dx 打包 DEX 字节码文件 | dalvikvm 命令 )(代码
我在将java的.class文件打包成.jar文件后运行不了“Failed to load Main-Class”。怎么办?