如有转载: http://blog.csdn.net/t12x3456

随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1">
    </embed>
  </div>
</div>
- UNEXPECTED TOP-LEVEL EXCEPTION:

- java.lang.IllegalArgumentException: method ID not in [<span class="number"></span>, <span class="number">0xffff</span>]: <span class="number">65536</span>

- at com.android.dx.merge.DexMerger$<span class="number">6</span>.updateIndex(DexMerger.java:<span class="number">501</span>)

- at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:<span class="number">282</span>)

- at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:<span class="number">490</span>)

- at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:<span class="number">167</span>)

- at com.android.dx.merge.DexMerger.merge(DexMerger.java:<span class="number">188</span>)

- at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:<span class="number">439</span>)

- at com.android.dx.command.dexer.Main.runMonoDex(Main.java:<span class="number">287</span>)

- at com.android.dx.command.dexer.Main.run(Main.java:<span class="number">230</span>)

- at com.android.dx.command.dexer.Main.main(Main.java:<span class="number">199</span>)

- at com.android.dx.command.Main.main(Main.java:<span class="number">103</span>)

 

没错,你的应用中的Dex 文件方法数超过了最大值65536的上限,简单来说,应用爆棚了.

**        那么让我们看一下为什么会引起这种错误:**

Android系统中,一个App的所有代码都在一个Dex文件里面。Dex是一个类似Jar的存储了多有Java编译字节码的归档文件。因为Android系统使用Dalvik虚拟机,所以需要把使用Java Compiler编译之后的class文件转换成Dalvik能够执行的class文件。这里需要强调的是,DexJar一样是一个归档文件,里面仍然是Java代码对应的字节码文件。当Android系统启动一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOptDexOpt的执行过程是在第一次加载Dex文件的时候执行的。这个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文件的效率要高很多。但是在早期的Android系统中,DexOpt有一个问题,也就是这篇文章想要说明并解决的问题。DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的Android系统中,DexOpt修复了这个问题,但是我们仍然需要对低版本的Android系统做兼容.

目前比较常用的方法:(1) 应用插件化,比如使用我正在参与开发的插件化框架 : https://github.com/singwhatiwanna/dynamic-load-apk ,如果有建议或者相关的问题,欢迎到Github上积极参与. (2) 分割Dex,多工程: 把所需要的.class文件或者是Jar文件和一些源码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。我们可以提前对它进行ODex操作,让它在被DexClassLoader加载的时候,跳过DexOpt的部分工作,从而加快加载的过程.(可参考facebook:https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920,这里边还可以看到在2.3上动态改变LinearAlloc缓冲的解决思路) 这两种方法并不冲突,插件化除了解决应用爆棚,还有很多其他的优点,可以看我之前的文章,不再复述.

当然,Google看来也意识到了目前应用方法数爆棚的问题, 目前在已经在API 21中提供了通用的解决方案,那就是android-support-multidex.jar. 这个jar包最低可以支持到API 4的版本(Android L及以上版本会默认支持mutidex).

让我们看一下如何应用android-support-multidex.jar(以下都以在Anroid studio中的使用为例,使用eclipse开发需要安装gradle插件,其他基本上相同):

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2">
    </embed>
  </div>
</div>
- afterEvaluate {

- tasks.matching {

- it.name.startsWith(<span class="string">&#8216;dex&#8217;</span>)

- }.each { dx ->

- <span class="keyword">if</span> (dx.additionalParameters == <span class="keyword">null</span>) {

- dx.additionalParameters = []

- }


- 
- <span class="comment">// optional</span>


- }

- }

但是默认的Dalvik 类加载器只会寻找classes.dex,所以需要将它们进行合并才能使得被识别

当然,现在有了android.support.multidex.jar的支持,一切都会变得非常简单,首先我们看一下相关源码的目录,具体的原理分析我会在之后的文章中进行讲解:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3">
    </embed>
  </div>
</div>
- android/support/multidex/BuildConfig.<span class="keyword">class</span>

- android/support/multidex/MultiDex$V14.<span class="keyword">class</span>

- android/support/multidex/MultiDex$V19.<span class="keyword">class</span>

- android/support/multidex/MultiDex$V4.<span class="keyword">class</span>

- android/support/multidex/MultiDex.<span class="keyword">class</span>

- android/support/multidex/MultiDexApplication.<span class="keyword">class</span>

- android/support/multidex/MultiDexExtractor$<span class="number">1</span>.<span class="keyword">class</span>

- android/support/multidex/MultiDexExtractor.<span class="keyword">class</span>

- android/support/multidex/ZipUtil$CentralDirectory.<span class="keyword">class</span>

- android/support/multidex/ZipUtil.<span class="keyword">class</span>

具体集成:
将如下配置加入工程 classPath

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4">
    </embed>
  </div>
</div>
- repositories {

- jcenter()

- }

- 
- dependencies {

- compile <span class="string">&#8216;com.google.android:multidex:0.1&#8217;</span>

- }

MultiDex实现原理:

Apk在运行的时候,有一个dexpathlist,而Multidex的源码中,会根据你的系统版本号对dexpathlist做修改,将所有的dex都添加到dexpathlist中.

接下来集成有两个步骤:

一. 从sdk\extras\android\support\multidex\library\libs 目录将android-support-multidex.jar导入工程中

二. 如果你的工程中已经含有Application类,那么让它继承android.support.multidex.MultiDexApplication类,

**     如果你的Application已经继承了其他类并且不想做改动,那么还有另外一种使用方式,覆写attachBaseContext()方法:**

 

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_5" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_5">
    </embed>
  </div>
</div>
- <span class="keyword">public</span> <span class="keyword">class</span> MyApplication <span class="keyword">extends</span> FooApplication {

- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> attachBaseContext(Context base) {

- <span class="keyword">super</span>.attachBaseContext(base);

- MultiDex.install(<span class="keyword">this</span>);

- }

- }

 

最后给出build.gradle中的完整配置:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_6" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_6">
    </embed>
  </div>
</div>
- android {

- compileSdkVersion <span class="number">21</span>

- buildToolsVersion <span class="string">&#8220;21.1.0&#8221;</span>

- 
- defaultConfig {

- &#8230;

- minSdkVersion <span class="number">14</span>

- targetSdkVersion <span class="number">21</span>

- &#8230;

- 
- <span class="comment">// Enabling multidex support.</span>

- multiDexEnabled <span class="keyword">true</span>

- }

- &#8230;

- }

- 
- dependencies {

- compile <span class="string">&#8216;com.android.support:multidex:1.0.0&#8217;</span>

- }

 

使用MutiDex的主意事项

一. 如果你继承了MutiDexApplication或者覆写了Application中的attachBaseContext()方法.

Application类中逻辑的注意事项:

Application 中的静态全局变量会比MutiDexinstal()方法优先加载,所以建议避免在Application类中使用静态变量引用main classes.dex文件以外dex文件中的类,可以根据如下所示的方式进行修改:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_7" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_7">
    </embed>
  </div>
</div>
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> onCreate() {

- <span class="keyword">super</span>.onCreate();

- 
- <span class="keyword">final</span> Context mContext = <span class="keyword">this</span>;

- <span class="keyword">new</span> Runnable() {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> run() {

- <span class="comment">// put your logic here!</span>

- <span class="comment">// use the mContext instead of this here</span>

- }

- }.run();

- }

 

二. 虽然Google解决了应用总方法数限制的问题,但并不意味着开发者可以任意扩大项目规模。Multidex仍有一些限制:

  1. DEX文件安装到设备的过程非常复杂,如果第二个DEX文件太大,可能导致应用无响应。此时应该使用ProGuard减小DEX文件的大小。
  2. 由于Dalvik linearAlloc的Bug,应用可能无法在Android 4.0之前的版本启动,如果你的应用要支持这些版本就要多执行测试。
  3. 同样因为Dalvik linearAlloc的限制,如果请求大量内存可能导致崩溃。Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。
  4. Multidex构建工具还不支持指定哪些类必须包含在首个DEX文件中,因此可能会导致某些类库(例如某个类库需要从原生代码访问Java代码)无法使用。

避免应用过大、方法过多仍然是Android开发者要注意的问题。Mihai Parparita的开源项目dex-method-counts可以用于统计APK中每个包的方法数量。

通常开发者自己的代码很难达到这样的方法数量限制,但随着第三方类库的加入,方法数就会迅速膨胀。因此选择合适的类库对Android开发者来说尤为重要。

开发者应该避免使用Google Guava这样的类库,它包含了13000多个方法。尽量使用专为移动应用设计的Lite/Android版本类库,或者使用小类库替换大类库,例如用Google-gson替换Jackson JSON。而对于Google Protocol Buffers这样的数据交换格式,其标准实现会自动生成大量的方法。采用Square Wire的实现则可以很好地解决此问题。

 

常见问题

DexException: Library dex files are not supported in multi-dex mode,你可能会见到如下的错误:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_8" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_8">
    </embed>
  </div>
</div>
- Error:Execution failed <span class="keyword">for</span> task <span class="string">&#8216;:app:dexDebug&#8217;</span>.

- > com.android.ide.common.internal.LoggedErrorException: Failed to run command:


- &#8230;

- Error Code:

- <span class="number">2</span>

- Output:

- UNEXPECTED TOP-LEVEL EXCEPTION:

- com.android.dex.DexException: Library dex files are not supported in multi-dex mode

- at com.android.dx.command.dexer.Main.runMultiDex(Main.java:<span class="number">322</span>)

- at com.android.dx.command.dexer.Main.run(Main.java:<span class="number">228</span>)

- at com.android.dx.command.dexer.Main.main(Main.java:<span class="number">199</span>)

- at com.android.dx.command.Main.main(Main.java:<span class="number">103</span>)
**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_9" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_9">
    </embed>
  </div>
</div>
- android {

- <span class="comment">// &#8230;</span>

- dexOptions {

- preDexLibraries = <span class="keyword">false</span>

- }

- }

OutOfMemoryError: Java heap space

当运行时如果看到如下错误:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_10" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_10">
    </embed>
  </div>
</div>
- UNEXPECTED TOP-LEVEL ERROR:

- java.lang.OutOfMemoryError: Java heap space

dexOptions中有一个字段用来增加java堆内存大小:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_11" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_11">
    </embed>
  </div>
</div>
- android {

- <span class="comment">// &#8230;</span>

- dexOptions {

- javaMaxHeapSize <span class="string">&#8220;2g&#8221;</span>

- }

- }

- 

 

使用eclipse的开发人员可以参考Google官方文档

https://developer.android.com/tools/building/multidex.html#mdex-gradle

需要安装支持gradle构建的插件,下载地址:

http://dist.springsource.com/release/TOOLS/gradle  (目前可能需要翻墙)

参考相关资料:

1. MutiDex 官方文档: https://developer.android.com/reference/android/support/multidex/MultiDex.html

2. http://blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html

另附android -support-mutidex.jar下载地址: http://download.csdn.net/detail/t12x3456/8143383

补充注意事项:

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_12" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_12">
    </embed>
  </div>
</div>
- com.android.dex.DexException: Multiple dex files define L{<span class="keyword">package</span>}/BuildConfig;

如果遇到这个错误请进行如下检查:

1. 主工程与依赖library工程包名是否重复
2. 检查主工程与依赖library工程是否含有重复的support.jar或者其他jar包

解决方案:

1. 修改library工程包名

2. 删除重复jar包

3.手工添加lib包,增加如下配置

**[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork)
  <div>
    <embed id="ZeroClipboardMovie_13" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_13">
    </embed>
  </div>
</div>
- dependencies {

- compile fileTree(dir: <span class="string">&#8216;libs&#8217;</span>, include: [<span class="string">&#8216;*.jar&#8217;</span>])

- compile project(<span class="string">&#8216;:lib-project-module&#8217;</span>)

- }

 

💬 评论