命令行指定的类打入单独的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主要有以下步骤:

  1. 编写java代码
  2. 编译成class
  3. 打包成jar
  4. 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”。怎么办?