multidex使用_android应用使用multidex突破64k方法数限制 - CSDN
精华内容
参与话题
  • 一、概述 Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,其中包含用来运行您的应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内可引用的方法总数限制在 65,536...
    一、概述

    Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,其中包含用来运行您的应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内可引用的方法总数限制在 65,536,其中包括 Android 框架方法、库方法以及您自己代码中的方法。在计算机科学领域内,术语千(简称 K)表示 1024(或 2^10)。由于 65,536 等于 64 X 1024,因此这一限制也称为“64K 引用限制”,详细介参考谷歌官方配置文档。
    谷歌官方配置使用文档

    二、Multidex使用简单配置
    // 1. Gralde 配置
    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    

    dependencies {
    api ‘com.android.support:multidex:1.0.3’
    api ‘com.android.support:multidex-instrumentation:1.0.3’
    }

    // 2. 清单配置
    <?xml version=“1.0” encoding=“utf-8”?>
    <manifest xmlns:android=http://schemas.android.com/apk/res/android
    package=“com.example.myapp”>

    <application
    android:name=“com.xxx.MyApplication” >


    </application>
    </manifest>

    // 3. Application初始化
    public class MyApplication extends SomeOtherApplication {
    @Override
    protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
    }
    }

    如果就这么简单我写这个也没什么意义,最主要看下面的高配置,和我所遇到的坑。

    四、高级配置,multiDexKeepFile 属性

    如果APP安装失败,调试日志出现 NoClassDefFoundErrorClassNotFoundExceptionNoSuchMethodException等异常,就需要将这些找不到的类,配置到主的DEX 文件中,以至于在APP初始化的时候能找到这些类。
    配置方法:

    1. 在build.gradle文件同级目录下创建一个multidex-config.txt(这里随便命名)配置文件。

    2. 在文件中添加找不到的类,如下所示:
      com/example/MyClass.class// 普通类配置方式
      com/example/MyOtherClass$InnerClass.class// 内部类配置方式

    3. 在配置multidex-config.txt前,先release一遍,找到app/build/intermediates/multi-dex/release/maindexlist.txt这个文件的所有内容复制到multidex-config.txt文件中。maindexlist.txt里面的内容是通过一系统列方法算出APP启动所关联的类。

      maindexlist文件目录.jpg

    4. 在build.gradle文件配置

    android {
        buildTypes {
            release {
                multiDexKeepFile file ('multidex-config.txt')// 小括号记得要加上,官方文档没有
                ...
            }
        }
    }
    
    1. 每个包内方法数上限配置(对低端机型很重要)
     dexOptions {
            javaMaxHeapSize "4g"
            preDexLibraries = false
            additionalParameters += '--multi-dex'
            additionalParameters += '--set-max-idx-number=35000'//每个包内方法数上限(根据实际项目配置这个数,来适配4.0-4.4一些低端机型,因为拆分的dex太大,这些低端机型加载不了dex)
            additionalParameters += '--minimal-main-dex'
        }
    

    注意:每个dex(claasses.dex、claasses1.dex、claasses2.dex...)内方法数上限(根据实际项目配置这个数,来适配4.0-4.4一些低端机型,因为拆分的dex太大,这些低端机型加载不了dex),这个数也不能太小,最多能分7个DEX。

    三、高级配置,multiDexKeepProguard 属性
    1. 目的是怕上面那些配置的类混淆导致APP启动失败,所配置,如果APP本来就没有混淆,就不用管这个配置了。
    2. 在build.gradle文件同级目录下创建一个multidex-config.pro(这里随便命名)配置文件。
    3. 配置内容如下所示(和混淆其实是一样):
      -keep class com.example.MyClass // 保持这个类不混淆
      -keep class com.example.** { *; }//保持这个包下的所有类不混淆
    4. build.gradle文件中配置
    android {
        buildTypes {
            release {
                multiDexKeepProguard ('multidex-config.pro')
                ...
            }
        }
    }
    
    五、总结
    1. Multidex使用有很多坑,尽量将自己的APP去三方平台做云真机兼容性测试。如阿里移动测试、testin云测试等,推荐去云测试上去做真机调试,调试完有调试日志。
    2. 在三方平台用真机调试一些不能过的机型,调试运行失败后,下载调试日志,就可以找到上面我讲的那几个错误。
    3. 如果没有类找不到那些异常,对于下面这些错误:
      DexOpt: --- END 'cn.xxx.xxx-1.apk.classes4.zip' --- status=0x000e, process failed
      MultiDex installation failure
      java.io.IOException: unable to open DEX file
      是Dex太大,在低端机型加载失败所致。如果你的Dex不能在小了,你就放弃这些机型吧(没办法了,如果有好的方法请告诉我)。
    4. 看一下我的测试成果,其实还是有些机型不行的,但对我们APP的用户来讲,应该是没什么影响的了。


      测试成果.jpg
    转自:https://www.jianshu.com/p/78f2e2d9484a
    展开全文
  • Android MultiDex 解析与使用

    千次阅读 2016-12-05 19:08:18
    Android MultiDex 解析与使用背景随着你的Apk不断的迭代更新,到你的apk到达一定大小的时候,你在编译apk的时候可能会出现下面的错误

    Android MultiDex 解析与使用

    背景

    随着你的Apk不断的迭代更新,到你的apk到达一定大小的时候,你在编译apk的时候可能会出现下面的错误:

    Conversion to Dalvik format failed:
    Unable to execute dex: method ID not in [0, 0xffff]: 65536

    最近的apk编译出现的错误日志如下:

    trouble writing output:
    Too many field references: 131000; max is 65536.
    You may try using --multi-dex option.

    这个编译错误产生的主要原因是因为每个DEX文件中方法数是以一个short类型结构保存,这样就限制了每个DEX的方法数大小。
    Dex主要组成:
    - Android FrameWork 方法数
    - Lib 方法数
    - 你自己写的代码方法数

    解决方法:
    • Android5.0以下:
      由于Android5.0以下使用的是Dalvik虚拟机,Dalvik虚拟机在Apk运行的时候去load Dex文件,所以我们可以使用Google提供的multiDex的那个jar包进行分包和合包,在Apk启动的时候进行多dex合包过程。

    • Android5.0以上:
      Android5.0以上我们默认是实用ART虚拟机,ART虚拟机是直接支持Dex合包生成.oat文件,在apk启动后直接加在oat文件。

    MultiDex使用
    • 如果你的APk的minSdk是21或者更高,这个时候你只要在build.gradle中进行如下配置:
    android {
        defaultConfig {
            ...
            minSdkVersion 21 
            targetSdkVersion 25
            multiDexEnabled true
        }
        ...
    }
    • 如果你的APK的minSdk是20及以下,这个时候就要使用multidex support library ,具体配置如下:
    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 25
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
      compile 'com.android.support:multidex:1.0.1'
    }

    重写或者配置Application:
    1. 当你没有重写Application的时候,你要在AndroidManifest.xml中进行如下配置:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapp">
        <application
                android:name="android.support.multidex.MultiDexApplication" >
            ...
        </application>
    </manifest>

    2.如果你重写了Application,你可以直接用继承MultiDexApplication,具体如下所示:

    public class MyApplication extends MultiDexApplication { ... }

    也可以不继承MultiDexApplication,直接在Application的初始化中对MultiDex进行初始化

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

    上面几个步骤完成了MultiDex的初始化,接下来要完成分包dex的配置

    我们可以动态的配置哪些文件加在到第一个dex文件中,具体配置如下:

    1. 首先我们创建一个文件,在这个文件中配置mainDex中包含的java文件:
    2. 然后再在build.gradle中配置一下:
    com/example/MyClass.class
    com/example/MyOtherClass.class
    android {
        buildTypes {
            release {
                multiDexKeepFile file('dex.keep')
                ...
            }
        }
    }

    以上就完成了MultiDex的配置工作了,现在65535的问题就基本解决了。

    总结

    目前我们解决65535主要有如下方法:
    1. 缩减代码,减小apk大小,这是治标不治本的方法。
    2. 使用多Dex技术,这个技术导致冷加载时常变长,有的时候还会出现一些的崩溃

    展开全文
  • Android的multidex使用及优化

    千次阅读 2016-03-22 12:17:12
    ...在安卓社区中,65k方法数的限制是一个被多次提及的问题。目前解决这个问题的办法就是用multidex。虽然multidex是谷歌给出的一个非常棒的办法,但是我发现了它对app启动性能存在严重的影响,这

    文章转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1223/3796.html



    引言

    在安卓社区中,65k方法数的限制是一个被多次提及的问题。目前解决这个问题的办法就是用multidex。虽然multidex是谷歌给出的一个非常棒的办法,但是我发现了它对app启动性能存在严重的影响,这点还没有在社区引起重视。我这篇文章的就是为那些还没有听说过这个问题(但是想使用multidexing)的开发者以及那些使用了multidexing,但是想观察本文解决办法所能能赢得性能的伙伴而写的。

    blob.png

    背景

    先为外行做一下科普。安卓app是由被转换成一个.class文件的java写成的。然后这个class文件(以及任何jar依赖)被编译成单个classes.dex文件。然后这个dex文件和一个apk文件(即最终从app商店所下载的东西)所需要的任意资源相组合。

    更多参见 这里

    这种编译过程的一个缺陷是一个dex文件系统只允许最多有65k个方法。在安卓的早期,达到65k方法上限的应用解决这个问题的办法就是使用Proguard来减少无用的代码。但是,这个方法有局限,并且只是为生产app拖延了接近65k限制的时间。

    为了解决这个问题,谷歌在最近的兼容库中放出了一个针对65k方法限制的解决方案:multidexing。这个方法非常方便并且允许你突破65k方法限制,但是(就如我之前说的),对性能有一个非常严重的影响,可能会减慢app的启动速度。

    设置multidex

    multidex是一个文档齐全的成熟的解决方案。我强烈推荐遵循安卓开发者网站上的指示来启用multidex。你也可以参考我的github上的项目样例

    NoClassDefFoundError?!

    在为项目配置multidexing 的时候,你可能会在运行的时候看到java.lang.NoClassDefFoundError。这意味着app启动的class不在main dex文件中。Android SDK Build Tools 21.1或者更高版本中的Gradle  Android 插件有对multidex 的支持。这个插件使用Proguard 来分析你的项目并在 [buildDir]/intermediates/multi-dex/[buildType]/maindexlist.txt文件中生成一个app启动classes 的列表。但是这个列表并不是100%准确,可能会丢失一些app启动所需的classes 。

    YesClassDefFound

    为了解决这个问题,你应该在multidex.keep 文件中罗列出那些class,以便让编译器知道在main dex文件中要保持哪些class。

    • 在工程目录中创建一个multidex.keep文件。

    • java.lang.NoClassDefFoundError中报告的class列举到multidex.keep文件。(注意: 不要直接修改build目录里的maindexlist.txt ,这个文件每次在编译的时候都会生成)。

    • 添加如下脚本到build.gradle。这个脚本将在编译项目的时候把multidex.keep 和 由Gradle生成的maindexlist.txt 结合在一起。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    android.applicationVariants.all { variant ->
        task "fix${variant.name.capitalize()}MainDexClassList" << {
            logger.info "Fixing main dex keep file for $variant.name"
            File keepFile = new File("$buildDir/intermediates/multi-dex/$variant.buildType.name/maindexlist.txt")
            keepFile.withWriterAppend { w ->
                // Get a reader for the input file
                w.append('\n')
                new File("${projectDir}/multidex.keep").withReader { r ->
                    // And write data from the input into the output
                    w << r << '\n'
                }
                logger.info "Updated main dex keep file for ${keepFile.getAbsolutePath()}\n$keepFile.text"
            }
        }
    }
    tasks.whenTaskAdded { task ->
        android.applicationVariants.all { variant ->
            if (task.name == "create${variant.name.capitalize()}MainDexClassList") {
                task.finalizedBy "fix${variant.name.capitalize()}MainDexClassList"
            }
        }
    }

    multidex app启动性能问题

    如果你使用multidex,你需要意识到它对app启动性能有影响。我们通过跟踪app的启动时间发现了这个问题-用户点击app图标到所有图片都下载完并显示给用户的这段时间。一旦multidex 启用,在所有运行Kitkat (4.4) 及以下的设备上我们的app启动时间就会大约增加15%。更多信息参考 Carlos SessaLazy Loading Dex files 。

    这是因为Android 5.0 以及更高版本使用了一个叫做ART的运行时,它天生就支持从应用的apk文件中加载multiple dex文件。

    解决multidex app启动性能问题

    在app启动到所有图片显示的间隙,存在着许多没有被Proguard 检测到的class,因此它们也就没有被存进main dex文件中。现在的问题是,我们如何才能知道在app启动期间什么样的calss被加载了呢?

    幸运的是,在 ClassLoader中我们有 findLoadedClass 方法。我们的办法就是在app启动结束的时候做一次运行时检查。如果第二个dex 文件中存有任何在app启动期间加载的class,那么就通过添加calss  name 到multidex.keep文件中的方式来把它们移到main dex文件中。我的 项目案例  中有实现的细节,但是你也可以这样做:

    • 在你认为app启动结束的地方运行下面util类中的getLoadedExternalDexClasses 

    • 把上面这个方法返回的列表添加到你的 multidex.keep 文件然后重新编译。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    public class MultiDexUtils {
        private static final String EXTRACTED_NAME_EXT = ".classes";
        private static final String EXTRACTED_SUFFIX = ".zip";
     
        private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator +
                "secondary-dexes";
     
        private static final String PREFS_FILE = "multidex.version";
        private static final String KEY_DEX_NUMBER = "dex.number";
     
        private SharedPreferences getMultiDexPreferences(Context context) {
            return context.getSharedPreferences(PREFS_FILE,
                    Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
                            ? Context.MODE_PRIVATE
                            : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
        }
     
        /**
         * get all the dex path
         *
         * @param context the application context
         * @return all the dex path
         * @throws PackageManager.NameNotFoundException
         * @throws IOException
         */
        public List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
            final ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
            final File sourceApk = new File(applicationInfo.sourceDir);
            final File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
     
            final List<String> sourcePaths = new ArrayList<>();
            sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
     
            //the prefix of extracted file, ie: test.classes
            final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
            //the total dex numbers
            final int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
     
            for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
                //for each dex file, ie: test.classes2.zip, test.classes3.zip...
                final String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
                final File extractedFile = new File(dexDir, fileName);
                if (extractedFile.isFile()) {
                    sourcePaths.add(extractedFile.getAbsolutePath());
                    //we ignore the verify zip part
                else {
                    throw new IOException("Missing extracted secondary dex file '" +
                            extractedFile.getPath() + "'");
                }
            }
     
            return sourcePaths;
        }
     
        /**
         * get all the external classes name in "classes2.dex", "classes3.dex" ....
         *
         * @param context the application context
         * @return all the classes name in the external dex
         * @throws PackageManager.NameNotFoundException
         * @throws IOException
         */
        public List<String> getExternalDexClasses(Context context) throws PackageManager.NameNotFoundException, IOException {
            final List<String> paths = getSourcePaths(context);
            if(paths.size() <= 1) {
                // no external dex
                return null;
            }
            // the first element is the main dex, remove it.
            paths.remove(0);
            final List<String> classNames = new ArrayList<>();
            for (String path : paths) {
                try {
                    DexFile dexfile = null;
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                        //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    else {
                        dexfile = new DexFile(path);
                    }
                    final Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                        classNames.add(dexEntries.nextElement());
                    }
                catch (IOException e) {
                    throw new IOException("Error at loading dex file '" +
                            path + "'");
                }
            }
            return classNames;
        }
     
        /**
         * Get all loaded external classes name in "classes2.dex", "classes3.dex" ....
         * @param context
         * @return get all loaded external classes
         */
        public List<String> getLoadedExternalDexClasses(Context context) {
            try {
                final List<String> externalDexClasses = getExternalDexClasses(context);
                if (externalDexClasses != null && !externalDexClasses.isEmpty()) {
                    final ArrayList<String> classList = new ArrayList<>();
                    final java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass"new Class[]{String.class});
                    m.setAccessible(true);
                    final ClassLoader cl = context.getClassLoader();
                    for (String clazz : externalDexClasses) {
                        if (m.invoke(cl, clazz) != null) {
                            classList.add(clazz.replaceAll("\\.""/").replaceAll("$"".class"));
                        }
                    }
                    return classList;
                }
            catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    结论

    这里是我们在多个设备上观察到的启动性能的提升效果。第一列(蓝色)是没有multidexing的基准app启动时间。你可以在第二列(红色)看到明显的增加,这是启用了multidex但没有其它任何额外工作的app启动时间。第三列(绿色)是开启了multidex 并且使用了我们提升方法的app启动时间。就如图中所看到的,app启动时间降到了multidex开启之前的水平,甚至更低。自己试试吧,你应该也能观察到性能的提升。

    blob.png


    后记

    仅仅因为你能并不意味着你应该。你应该把multidex看成最后的办法因为它对app启动时间存在很大影响而且要解决这个问题你需要维护额外的代码并解决奇怪的错误(比如: java.lang.NoClassDefFoundError)。一旦达到了65k方法数的限制,我们应该先避免去使用multidex以防止性能问题。我们不断的检查使用的sdk找出许多可以移除或者重构的无用代码。只有此时仍然没有办法的时候我们才考虑multidex。那时我们的代码质量也会有个质的飞跃。不要直接使用multidex,要先保持代码的干净,复用现有组建,或者重构代码来避免65k方法数限制。




    展开全文
  • Google MultiDex可以解决method id超过65536的问题,但是使用MultiDex又无形之中可能引进了一些坑,比较常见的就是crash和ANR,比如Could not find class,NoClassDefFoundError, Could not find method,那么要如何...

    Android开发的朋友,如果是在开发一款中大型应用时,都会碰到这么一个问题,就是dex分拆问题, google给出的解决方案MultiDex。

    现象:

    有些APP本身功能比较多,再加上一些其它三方的SDK,慢慢的发现dex越来越大,直到有一天编译出现如下错误:

    Error:The number of method references in a .dex file cannot exceed 64K.
    Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html

    错误原因比较明确了,打开Gradle Console查看详细信息,我们在一堆错误中找到如下提醒:

    "UNEXPECTED TOP-LEVEL EXCEPTION:\ncom.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536\n\tat com.android.dx.merge.DexMerger$6.updateIndex(DexMerger

    原因:

    问题原因是因为早期Android系统设计时用一个short来表示dex里的每个method的id,我们知道short的上限是64K,所以就遗留了这样一个问题,既然存在了,肯定要找方法解决,Google刚开始给出建议是使用proguard,但是再怎么proguard也还是会突破64K的,所以后面Google给出了MultiDex方案。就是我们经常看到的一个apk里,有classes.dex, classes2.dex,甚至还有classes3.dex等。

    MultiDex实现步骤:

    用这种方法来突破64K的method id数量的限制,具体实现步骤如下:
    1. 在Module的build.gradle里添加
    multiDexEnabled true

    例如:

    android {
        compileSdkVersion 22
        buildToolsVersion "22.0.1"
    
        defaultConfig {
            applicationId "x.x.x"
            minSdkVersion 15
            targetSdkVersion 22
            versionCode 17
            versionName "1.2.9"
    
            //添加这一行
            multiDexEnabled true
    
        }
    
    }

    2. 接着在Module的build.gradle里添加

    dependencies {
      compile 'com.android.support:multidex:1.0.0'
    }

    3.第三步有两种情况,
    1)如果你的apk没有定义application,则在AndroidManifest.xml里的application里做如下修改:
    添加MultiDexApplication

    <application
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme"
            android:name="android.support.multidex.MultiDexApplication"
            tools:replace="android:icon, android:name"
           >

    2)如果apk有自己的appliation,比如叫MyApplication,则在MyApplication实现里加如下代码:

     @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            MultiDex.install(this);
        }
    

    至此,MultiDex的编译配置已经完成,你只要重新编译即可,apk就会生成两个(或多个) dex。如果有兴趣了解下MultiDex的实现原理,可以查看源代码,https://android.googlesource.com/platform/frameworks/multidex 源代码相对比较简单的。基本原理是使用java的class loader,这个在一些热修复技术上也在使用,比如企鹅工厂开源的Tinker。

    存在的坑及解决方法:

    虽然MultiDex分包已经完成,但是实际使用过程中,可能存在一些坑,比如之前笔者碰到的一个VerifyError的错误(http://blog.csdn.net/zhuobattle/article/details/47153025)
    1. 分拆导致的crash

    这个问题的表现除了报VerifyError外,还有可能报Could not find class,NoClassDefFoundError, Could not find method等。是因为我们在main dex中调用的函数或类被放在了classes2.dex中,而在classes2.dex还没有被完全加载前,调用这些api就会导致这种问题。要确认是否是这个问题导致的错误,我们可以查看:
    app\build\intermediates\multi-dex\debug\maindexlist.txt 这个文本文件,这里列出来的类都会被放在主dex中,那么问题是我们要如何解决这个问题呢?
    解决方案:
    首先,我们来看下在编译过程中,multidex是做了哪几步,这个打开Gradle Console可以找到multidex相关的步骤,其中一个步骤是关键就是生成maindexlist.txt的步骤:createDebugMainDexClassList就是这里生成maindexlist.txt 的,但是这个文件直接修改又没有什么用,因为每次编译都会重新生成一次的,笔者在实践者发现可以用自定义的方式:multiDexKeepFile file(‘multiDexKeep.txt’)
    例子:

    android {
        compileSdkVersion 22
        buildToolsVersion "22.0.1"
    
    
        defaultConfig {
            applicationId "x.x.x"
            minSdkVersion 15
            targetSdkVersion 22
            versionCode 17
            versionName "1.2.9"
    
            multiDexEnabled true
    
            //添加这一行
            multiDexKeepFile file('multiDexKeep.txt')
        }
    
    }

    内容和上面提到的createDebugMainDexClassList生成的maindexlist.txt一样即可,记得把这个multiDexKeep.txt文件放在app目录 下。multiDexKeep.txt内容可以如下:

    com/test/Util.class
    com/test/help/b.class

    实测起作用,被keep的class全都留在了classes.dex中(release版本要配合mapping使用)。

    2. ANR
    也就是我们常说的卡顿,为什么会卡顿呢?这里因为我们把multidex的install放在了attachBaseContext中,而这个调用又是在MainActivity的onCreate之前的,所以如果2.dex,3.dex第一次加载时间很长(生成odex文件会耗费一定的时间), 就有可能会导致第一次启动卡上一小会(实测在Dalvik手机上一般classes.dex比较小的情况下,卡顿不明显,线上就不好说了)。

    解决方法是在 APP第一次启动时(卸载、重装都会做一遍2odex,具体可以查看/data/data//code_cache/secondary-dexes/目录下的odex文件,所以这里判断要准确),把install放到异步线程里去做。这是很多网上的解决方法,但是如果这样做,你就必须再写一个类似initAfterDex2Installed(),来保证2.dex里的类不会提前被调用到,或者输出一个启动界面,停留几秒的样子,比如很多APP启动都有开机广告,或者开机画面,以此来解决app在2.dex加载之前部分功能无法使用的问题。网上大多是这种解决方案。本人测试后发现,如果MultiDex.install(this),放在后面或者异步来做的话,在MainActivity里的onCreate函数:
    setContentView这里就出错了,堆栈如下:

    java.lang.NoClassDefFoundError: android.support.v7.appcompat.R$attr
                                                           at android.support.v7.app.AppCompatDelegateImplV7.ensureSubDecor(AppCompatDelegateImplV7.java:289)
                                                           at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:246)
                                                           at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:106)
                                                           at com.cn.x.x.MainActivity.onCreate(MainActivity.java:86)
    

    很明显android.support.v7.appcompat.R$attr在classes2.dex中,在调用时,还没有完成classes2.dex的加载,所以如果要解决的话,要不把这个类放到maindex中,要不让MainActivity的onCreate函数延迟调用。如果其它类还有类似问题,就要一个一个的试,成本有点高,有可能在不同系统,不同手机上还会出各种奇芭错误。由此看来,install延期加载方案并不合适。为了说明问题,也分析了网易几款大的产品,比如新闻、云音乐,全部是使用的mutlidex方式,都没有延期加载,全部是在application的attachBaseContext直接调用install.

    那么我们要如何解决 2.dex, 3.dex在第一次启动apk时,有可能产生的耗时呢?

    1)重新设计,或者在开发之前就设计好,使用插件化的方式来解决maindex的问题,把一些功能做成插件,保证这些dex在首屏启动时不需要被加载
    2) 自己实现多dex框架,有兴趣可以看下微信的实现框架,并没有使用MultiDex,而是使用自己的Tinker(一种动态加载dex的方案,也被用于热更新)
    微信使用的是TinkerAppliation. 另外一个例子是手机QQ的实现技术,手Q里居然有classes6.dex,也就是总共有6个dex,感兴趣的朋友可以分析下手Q的实现方案,笔者动态看过,基本上也是在手Q启动界面还没出来时,所有的dex会全部完成2odex的转换,在手机上第一次运行还是会花费不少时间的。而且笔者测试了很多手机,基本上2个dex不会发生anr,当然真实情况怎么样,还是要放到线上才能说明问题,也就是接收众多手机,不同品牌,不同性能的手机测试才知道,好在有一些知名公司的质量跟踪平台可以线上捕捉这些ANR,比如网易云捕

    3) ART以后的实际情况是,不管你分成几个dex,在安装时都已经全部完成OAT的转换,这样分dex至少在ART以后也就是5.0(部分4.4机型可以选ART模式)以后不存在调用install占用时间的问题,而且实际测试也是在5.0以后,即使不调用install(this)也能正常工作,为了证实,我们拿网易新闻测试,在安装完后,在目录 /data/dalvik-cache//data@app@com.netease.newsreader.activity-1@base.apk@classes.dex,这个oat文件有84M,所以你可以知道
    为什么第一次安装相比 dalivk慢了。
    打开这个文件,后缀虽然还是dex,其实是一个ELF文件,也就是OAT文件。
    这里写图片描述
    classes.dex在这里。
    这里写图片描述

    这里写图片描述
    classes2.dex, classes3.dex都已经被优化成OAT文件了。

    这些处理都已经在安装的时候完成,就不存在启动时再对classes2.dex和classes3.dex进行处理了,避免了第一次启动可能导致的ANR.

    总结

    总结一下就是我们可以通过自定义maindexlist来控制哪些类一定出现在main dex中,这样可以避免crash;
    而针对ANR还是不建议使用异步加载,合理设计和插件形式会比较合理,如果大家有其它更好的方法可以讨论。
    不过在ART以后,不管是classes.dex还是classes2.dex, classes3.dex在安装时就已经完成了OAT的转换了,由分包导致的ANR的可能性就小了很多。

    文章写完的时候,翻了一下其它博客,找到有一位朋友对ANR另外的一种解决方案,可以借鉴:
    http://blog.csdn.net/qq_17766199/article/details/51285868

    展开全文
  • MultiDex使用方法

    2016-05-12 09:52:09
    随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误: UNEXPECTED TOP-LEVEL EXCEPTION: ...
  • Multidex记录一:介绍和使用

    千次阅读 2019-11-15 10:16:14
    Multidex记录一:介绍和使用 Multidex记录二:缺陷&amp;amp;解决 Multidex记录三:源码解析 记录Multidex介绍和使用 为什么要用记录呢,因为我从开始接触Android时我们的项目就在65535的边缘。不久Google就出了...
  • MultiDex示例: AndroidStudio中MultiDex配置: 1、grandle配置 multiDexEnabled true // true时,生成多个dex setMultiDexKeepFile file("multiDexKeep.txt") // 当前项目中指定的classes,编译到...
  • android-support-multidex.jar

    千次下载 热门讨论 2020-07-29 14:20:40
    使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚.Google在API 21中提供了通用的解决方案,那就是android-support-multidex.jar. 这个jar包最低可以支持到API 4的版本(Android L及以上...
  • android MultiDex multiDex原理(一)

    万次阅读 2016-04-15 16:09:59
    android MultiDex 原理(一) Android分包MultiDex原理详解 MultiDex的产生背景 当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次...
  • 给App启用MultiDex功能

    千次阅读 2017-04-16 00:10:45
    App 启动 MultiDex 主要是为了解决 “65535 方法数超标” 以及 “INSTALL_FAILED_DEXOPT” 问题,就目前来说,对于使用 Android Studio 的朋友来说,MultiDex 这个术语应该不陌生。而对于那些从早期使用 Eclipse
  • 出现java.lang.NoClassDefFoundError异常,排除掉classpath设置问题,发现是multidex后导致的。 为什么要用multidex?项目中因方法数超过了65536,所以用到了MultiDex。 经过筛查,发现这里异常是因为multidex落下...
  • 在上一篇博客中,我给大家详细介绍了ant实现Android多分包技术,具体可以点击查看:彻底掌握Android多分包技术MultiDex-用Ant和Gradle分别构建(一);接下来的这篇博客我将带领大家一起学习使用Gradle构建Android...
  • 随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误: UNEXPECTED TOP-LEVEL EXCEPTION: ...
  • 在开启multiDex时,多个dex文件中的类是被gradle脚本分配的,而且app在启动时,首先加载的是MainDex。有时候我们需要设定某些类被放到MainDex中,如何设定? 在build.gradle中开启multiDex,并指定配置文件 ...
  • dex分包方案概述与multidex包的配置使用

    万次阅读 多人点赞 2019-03-19 15:37:40
    Android分包MultiDex原理 《Android开发艺术探索》博客中间会涉及到dex文件的反编译,参考博文: dex文件的反编译-dex2jar和jd-gui1.dex分包的原因对于功能越来越复杂的app的两大问题 问题一:当项目越来越大,...
  • Android multidex 使用 与 实现原理

    千次阅读 2019-09-08 21:50:50
    Android multidex 使用 与 实现原理 在Android中一个Dex文件最多存储65536个方法,也就是一个short类型的范围。但随着应用方法数量的不断增加,当Dex文件突破65536方法数量时,打包时就会抛出异常。 为解决该问题,...
  • 以这个错误 Error:Execution failed for task ':app:dexDebug'. ... Process 'command '/usr/lib/jvm/java-7-oracle/bin/j
  • classLoader 在加载 APK 的时候限制了class.dex 包含的 Java 方法数,其总数不能超过65535(64K,不要再说成 65K 了,1K = 2^10 = 1024 , 64 * 1024 = 65535),Google 官方给出的解决方案是使用 Multidex
  • 首先正确使用 google的multipartdex 修改Gradle,导入’com.android.support:multidex:1.0.0’,打开multiDexEnabled; android { compileSdkVersion 21 buildToolsVersion "21.1.0" defaultConfig { ... mi
  • dex分包之--------multidex包的配置使用

    千次阅读 2017-09-26 18:58:36
     首先说一下我遇到的情况,最近接手了一个项目是在已有的项目里进行更新添加一些功能,然后该项目导了N多的包,在我使用Android Studio的run”App”直接安装到手机上运行是正常的,然后正式打包安装后就崩溃了,...
1 2 3 4 5 ... 20
收藏数 5,090
精华内容 2,036
关键字:

multidex使用