android MultiDex multidex原理下超出方法数的限制问题(三)

简介: android MultiDex 原理下超出方法数的限制问题(三)    插件化?自动化?multiDex?是不是觉得已经懵逼了?请先看这篇文章的内容,在下篇文章中将会详解具体的过程… 随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目...

android MultiDex 原理下超出方法数的限制问题(三) 

 

插件化?自动化?multiDex?是不是觉得已经懵逼了?请先看这篇文章的内容,在下篇文章中将会详解具体的过程…

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

UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536
at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)
at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282)
at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)
at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)
at com.android.dx.merge.DexMerger.merge(DexMerger.java:188)
at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)
at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287)
at com.android.dx.command.dexer.Main.run(Main.java:230)
at com.android.dx.command.dexer.Main.main(Main.java:199)
at com.android.dx.command.Main.main(Main.java:103)

没错,你的应用中的Dex 文件方法数超过了最大值65536的上限。

简单来说,应用爆棚了.为什么是65536?不了解先看: android MultiDex 原理(一)


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

        在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/ssingwhatiwanna/sdynamic-load-apk ,如果有建议或者相关的问题,欢迎到Github上积极参与.

          (2) 分割Dex,多工程: 把所需要的.class文件或者是Jar文件和一些源码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。我们可以提前对它进行ODex操作,让它在被DexClassLoader加载的时候,跳过DexOpt的部分工作,从而加快加载的过程. 这两种方法并不冲突,插件化除了解决应用爆棚,还有很多其他的优点,可以看我之前的文章,不再复述.

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

让我们看一下如何应用android-support-multidex.jar(以下都以在Anroid studio中的使用为例):


首先可以用--multi-dex配置(build.gradle)进行解决,生成的Apk中将包含多个dex文件,比如classes.dex, classes2.dex. 如下,需要对build.gradle进行修改:
afterEvaluate {
    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
        if (dx.additionalParameters == null) {
            dx.additionalParameters = []
        }
        dx.additionalParameters += '--multi-dex' // enable multidex

        // optional
        // dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString() // enable the main-dex-list
    }
}



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

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

具体集成:
将如下配置加入工程  classPath
 
repositories {
  jcenter()
}

dependencies {
  compile 'com.google.android:multidex:0.1'
}



接下来集成有两个步骤:

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

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

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



public class MyApplication extends FooApplication {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}


最后给出build.gradle中的完整配置:
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.13.3'
        classpath 'jp.leafytree.gradle:gradle-android-scala-plugin:1.1'
    }
}

apply plugin: 'com.android.application'
apply plugin: 'jp.leafytree.android-scala'

repositories {
    jcenter()
}

android {
    compileSdkVersion 21
    buildToolsVersion '21.0.2'

    defaultConfig {
        applicationId 'some.app'
        minSdkVersion 19
        targetSdkVersion 19
        versionCode 1
        versionName '1.0'
    }
}

dependencies {
    compile 'com.google.android:multidex:0.1'
    compile 'com.android.support:support-v4:19.0.1'
    compile 'com.google.android.gms:play-services:5.0.77'
    compile 'org.scala-lang:scala-library:2.11.2'
    compile 'org.scaloid:scaloid_2.11:3.4-10'
}



使用MutiDex的主意事项


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

Application类中逻辑的注意事项:

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

@Override
    public void onCreate() {
        super.onCreate();

        final Context mContext = this;
        new Runnable() {

            @Override
            public void run() {
                // put your logic here!
                // use the mContext instead of this here
            }
        }.run();
    }


常见问题


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


Error:Execution failed for task ':app:dexDebug'.
> com.android.ide.common.internal.LoggedErrorException: Failed to run command:
    $ANDROID_SDK/build-tools/android-4.4W/dx --dex --num-threads=4 --multi-dex
    ...
  Error Code:
    2
  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:322)
        at com.android.dx.command.dexer.Main.run(Main.java:228)
        at com.android.dx.command.dexer.Main.main(Main.java:199)
        at com.android.dx.command.Main.main(Main.java:103)


  对于dex 的--multi-dex 选项设置与预编译的library工程有冲突,因此如果你的应用中包含引用的lirary工程,需要将预编译设置为false:
android {
    // ...
    dexOptions {
        preDexLibraries = false
    }
}



OutOfMemoryError: Java heap space

当运行时如果看到如下错误:
UNEXPECTED TOP-LEVEL ERROR:
java.lang.OutOfMemoryError: Java heap space

dexOptions 中有一个字段用来增加java堆内存大小:
android {
    // ...
    dexOptions {
        javaMaxHeapSize "2g"
    }
}    
        

后期持续更新中…


参考相关资料:

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

2.http://blog.csdn.net/djy1992/article/details/51162013

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


相关文章
|
3月前
|
存储 Shell Android开发
基于Android P,自定义Android开机动画的方法
本文详细介绍了基于Android P系统自定义开机动画的步骤,包括动画文件结构、脚本编写、ZIP打包方法以及如何将自定义动画集成到AOSP源码中。
78 2
基于Android P,自定义Android开机动画的方法
|
2月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
162 8
|
3月前
|
Android开发
基于android-11.0.0_r39,系统应用的手动签名方法和过程
本文介绍了基于Android 11.0.0_r39版本进行系统应用手动签名的方法和解决签名过程中遇到的错误,包括处理`no conscrypt_openjdk_jni-linux-x86_64`和`RegisterNatives failed`的问题。
185 2
|
25天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
48 15
Android 系统缓存扫描与清理方法分析
|
24天前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
51 8
|
1月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
38 3
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
47 2
|
2月前
|
编解码 前端开发 Android开发
Android经典实战之TextureView原理和高级用法
本文介绍了 `TextureView` 的原理和特点,包括其硬件加速渲染的优势及与其他视图叠加使用的灵活性,并提供了视频播放和自定义绘制的示例代码。通过合理管理生命周期和资源,`TextureView` 可实现高效流畅的图形和视频渲染。
230 12
|
1月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
24 0
|
3月前
|
Android开发
Android在rootdir根目录创建自定义目录和挂载点的方法
本文介绍了在Android高通平台的根目录下创建自定义目录和挂载点的方法,通过修改Android.mk文件并使用`LOCAL_POST_INSTALL_CMD`变量在编译过程中添加目录,最终在ramdisk.img的系统根路径下成功创建了`/factory/bin`目录。
207 1