Android-记一次bugfix-主dex包数超过65535

一.问题产生和解决过程

1.问题描述

打包的时候总是提示:

com.android.dex.DexException:Too many classes in --main-dex-list, main dex capacity exceeded
2.问题原因

因为主dex包中的方法数超过65535(Short.MAX_VALUE),导致生成包失败。

追溯问题:

if (args.mainDexListFile != null) {
  // with --main-dex-list
  // ...
  // forced in main dex
  for (int i = 0; i < fileNames.length; i++) {
    // call processClass
    processOne(fileNames[i], mainPassFilter);
  }
  if (dexOutputArrays.size() > 0) {
    throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
      + ", main dex capacity exceeded");
  }
}
3.问题解决
  1. 在app的build.gradle中添加:

     //支持多包打包
     multiDexEnabled = true
     //支持自定义主dex包中的类
     multiDexKeepProguard file('multiDexKeep.txt')
     
     dexOptions {
         javaMaxHeapSize "4g"
         keepRuntimeAnnotatedClasses false
         //限制一个dex包中最大的方法数 最大为65535  一般设置在58000到62000之间即可
         additionalParameters += '--set-max-idx-number=62000'
         jumboMode = true
     }
    
  2. 在gradle.properties中添加以下代码,主要是解决了不按照方法数限制分包的问题

     android.enableD8=false
     //虽然useDexArchive提示过期,但是该属性必须有
     android.useDexArchive=false
    

二.总结问题

1.Android 分包过程

Android系统安装运行应用的时候,有一步是对 dex 进行运行优化,增加运行效率,优化过程中,有过处理汇编文件加载的优化叫dexOpt。

dexOpt的执行过程:

在第一次加载Dex文件的时候执行的,这个过程会生成一个odex文件,即Optimised dex。odex的用途是分离程序资源和可执行文件、以及做预编译处理。执行 odex处理过的效率会比直接执行dex纯粹jar包文件的效率要高很多。dexOpt有一个设计,会把每一个类的方法id检索起来,存在一个链表结构里面。这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536(Short.MAX_VALUE)

Dalvik 模式下,dexOpt 肯定开启
ART 模式下,虽然不是JIT,仍然复用了这个优化策略

解决方法数超限的问题,需要将该dex文件拆成成多个。

官方文档:https://developer.android.com/tools/building/multidex.html#about

分包过程:

  1. 找出必须优先加载的类在主包
  2. 其余类按引用顺序,随机到其余包
  3. 打包构建出APP发布
  4. 运行分包的APP在 Applicaion 实例化之后,会检查系统版本是否支持 multidex
  5. 运行加载主包 Dalvik 模式将子包拷贝到应用的沙盒目录 ART 则是运行主包,混合子包生成 oat 文件
  6. Dalvik 运行时,先运行编译运行主包,然后通过反射将子dex注入到当前的 classloader 中
  7. ART 模式加载 包含多个dex的 oat 文件,然后解释运行主包,一样通过反射将子dex注入到当前的 classloader 中

具体的分包过程可以参考:https://blog.csdn.net/luoshengyang/article/details/39307813

关于Dalvik和ART:

  1. Android5.0之前,使用 Dalvik 方式运行,先加载主分包,然后反射加载其余的包
  2. Android5.0之后,使用 ART 方式运行,ART预编译时,扫描主分包和子包,生成 .oat 文件用于用户运行
2.如何自定义主dex包中的类

既然知道了android是通过分包来避免每个dex超过65535进行打包的,那要解决一个dex包中的方法太多就可以进行自定义主dex的内容,来避免方法数超过65535和必要的方法不在主dex中。
一般我们查看 \build\intermediates\multi-dex\debug\maindexlist.txt 或者 \build\intermediates\multi-dex\release\maindexlist.txt 就能看到里面的规则。如果你使用了tinker做热更新,应该会看到tinker自己写了脚本对maindexlist.txt文件进行一个修改。

afterEvaluate {
tasks.matching {
    it.name.startsWith("dex")
}.each { dx ->
    if (dx.additionalParameters == null) {
        dx.additionalParameters = []
    }
// 允许生成多个dex文件
dx.additionalParameters += '--multi-dex' // enable multidex
// 设置multidex.keep文件中class为第一个dex文件中包含的class
dx.additionalParameters += "--main-dex-list=$projectDir/multidex.keep".toString()
// 添加后第一个classes.dex文件只能包含-main-dex-list列表中class
// 没有这设置此项无作用
dx.additionalParameters += "--minimal-main-dex"
}
}

规则:

  • 基于apk运行的加载机制,Application 中的引用肯定在在主包内
  • 开启 MultiDex 分包 那么android.support.multidex 包肯定必须在主包内
  • 继承 java.lang.annotation.Annotation android.app.backup.BackupAgent 在主包
  • 在 AndroidManifest.xml 注册的四大组件,必须在第一个包
  • 使用 instrumentation 测试技术实现的必须在第一个包内
最后

虽然上面的手段可以一时解决问题,至少解决了我的打包问题,但是这些都是治标不治本的。我们写代码还是要注意代码的整洁,删除无用的文件,重复的轮子,四大组件中尽量不写业务代码。

参考:
https://www.jianshu.com/p/b4c179ad137e
https://www.jianshu.com/p/27319854cd45

你可能感兴趣的:(Android,bugfix)