65536

trouble writing output: Too many method references: 70048; max is 65536.
或者
UNEXPECTED TOP-LEVEL EXCEPTION:

java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536
at com.android.dx.merge.DexMerger6.updateIndex(DexMerger.java:501)atcom.android.dx.merge.DexMerger6.updateIndex(DexMerger.java:501) at com.android.dx.merge.DexMerger6.updateIndex(DexMerger.java:501)atcom.android.dx.merge.DexMergerIdMerger.mergeSorted(DexMerger.java:276)
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):Derp:dexDerpDebug FAILED

编译环境

buildscript {
repositories {
jcenter()
}
dependencies {
classpath ‘com.android.tools.build:gradle:1.3.0’
}
}

android {
compileSdkVersion 23
buildToolsVersion “25.0.3”
//…
defaultConfig {
minSdkVersion 14
targetSdkVersion 23
//…
}
}

为什么是65536

根据 StackOverFlow – Does the Android ART runtime have the same method limit limitations as Dalvik? 上面的说法,是因为 Dalvik 的 invoke-kind 指令集中,method reference index 只留了 16 bits,最多能引用 65535 个方法。Dalvik bytecode :

  • 即使 dex 里面的引用方法数超过了 65536,那也只有前面的 65536 得的到调用。所以这个不是 dex 的原因。其次,既然和 dex 没有关系,那在打包 dex 的时候为什么会报错。我们先定位 Too many 关键字,定位到了 MemberIdsSection :

public abstract class MemberIdsSection extends UniformItemSection {
/** {@inheritDoc} */
@Override
protected void orderItems() {
int idx = 0;

if (items().size() > DexFormat.MAX_MEMBER_IDX + 1) {
throw new DexIndexOverflowException(getTooManyMembersMessage());
}

for (Object i : items()) {
((MemberIdItem) i).setIndex(idx);
idx++;
}
}

private String getTooManyMembersMessage() {
Map<String, AtomicInteger> membersByPackage = new TreeMap<String, AtomicInteger>();
for (Object member : items()) {
String packageName = ((MemberIdItem) member).getDefiningClass().getPackageName();
AtomicInteger count = membersByPackage.get(packageName);
if (count == null) {
count = new AtomicInteger();
membersByPackage.put(packageName, count);
}
count.incrementAndGet();
}

Formatter formatter = new Formatter();
try {
String memberType = this instanceof MethodIdsSection ? “method” : “field”;
formatter.format(“Too many %s references: %d; max is %d.%n” +
Main.getTooManyIdsErrorMessage() + “%n” +
“References by package:”,
memberType, items().size(), DexFormat.MAX_MEMBER_IDX + 1);
for (Map.Entry<String, AtomicInteger> entry : membersByPackage.entrySet()) {
formatter.format("%n%6d %s", entry.getValue().get(), entry.getKey());
}
return formatter.toString();
} finally {
formatter.close();
}
}
}

items().size() > DexFormat.MAX_MEMBER_IDX + 1 ,那 DexFormat 的值是:

public final class DexFormat {
/**

  • Maximum addressable field or method index.
  • The largest addressable member is 0xffff, in the “instruction formats” spec as field@CCCC or
  • meth@CCCC.
    */
    public static final int MAX_MEMBER_IDX = 0xFFFF;
    }

dx 在这里做了判断,当大于 65536 的时候就抛出异常了。所以在生成 dex 文件的过程中,当调用方法数不能超过 65535 。那我们再跟一跟代码,发现 MemberIdsSection 的一个子类叫 MethodidsSection :

public final class MethodIdsSection extends MemberIdsSection {}

回过头来,看一下 orderItems() 方法在哪里被调用了,跟到了 MemberIdsSection 的父类 UniformItemSection :

public abstract class UniformItemSection extends Section {
@Override
protected final void prepare0() {
DexFile file = getFile();

orderItems();

for (Item one : items()) {
one.addContents(file);
}
}

protected abstract void orderItems();
}

再跟一下 prepare0 在哪里被调用,查到了 UniformItemSection 父类 Section :

public abstract class Section {
public final void prepare() {
throwIfPrepared();
prepare0();
prepared = true;
}

protected abstract void prepare0();
}

那现在再跟一下 prepare() ,查到 DexFile 中有调用:

public final class DexFile {
private ByteArrayAnnotatedOutput toDex0(boolean annotate, boolean verbose) {
classDefs.prepare();
classData.prepare();
wordData.prepare();
byteData.prepare();
methodIds.prepare();
fieldIds.prepare();
protoIds.prepare();
typeLists.prepare();
typeIds.prepare();
stringIds.prepare();
stringData.prepare();
header.prepare();
//blablabla…
}
}

那再看一下 toDex0() 吧,因为是 private 的,直接在类中找调用的地方就可以了:

public final class DexFile {
public byte[] toDex(Writer humanOut, boolean verbose) throws IOException {
boolean annotate = (humanOut != null);
ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);

if (annotate) {
result.writeAnnotationsTo(humanOut);
}

return result.getArray();
}

public void writeTo(OutputStream out, Writer humanOut, boolean verbose) throws IOException {
boolean annotate = (humanOut != null);
ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);

if (out != null) {
out.write(result.getArray());
}

if (annotate) {
result.writeAnnotationsTo(humanOut);
}
}
}

先搜搜 toDex() 方法吧,最终发现在 com.android.dx.command.dexer.Main 中:

public class Main {
private static byte[] writeDex(DexFile outputDex) {
byte[] outArray = null;
//blablabla…
if (args.methodToDump != null) {
outputDex.toDex(null, false);
dumpMethod(outputDex, args.methodToDump, humanOutWriter);
} else {
outArray = outputDex.toDex(humanOutWriter, args.verboseDump);
}
//blablabla…
return outArray;
}
//调用writeDex的地方
private static int runMonoDex() throws IOException {
//blablabla…
outArray = writeDex(outputDex);
//blablabla…
}
//调用runMonoDex的地方
public static int run(Arguments arguments) throws IOException {
if (args.multiDex) {
return runMultiDex();
} else {
return runMonoDex();
}
}
}

args.multiDex 就是是否分包的参数,那么问题找着了,如果不选择分包的情况下,引用方法数超过了 65536 的话就会抛出异常。

同样分析第二种情况,根据错误信息可以具体定位到代码,但是很奇怪的是 DexMerger ,我们没有设置分包参数或者其他参数,为什么会有 DexMerger ,而且依赖工程最终不都是 aar 格式的吗?那我们还是来跟一跟代码吧。

public class Main {
private static byte[] mergeLibraryDexBuffers(byte[] outArray) throws IOException {
ArrayList dexes = new ArrayList();
if (outArray != null) {
dexes.add(new Dex(outArray));
}
for (byte[] libraryDex : libraryDexBuffers) {
dexes.add(new Dex(libraryDex));
}
if (dexes.isEmpty()) {
return null;
}
Dex
merged = new DexMerger(dexes.toArray(new Dex[dexes.size()]), CollisionPolicy.FAIL).merge();
return merged.getBytes();
}
}

这里可以看到变量 libraryDexBuffers ,是一个 List 集合,那么我们看一下这个集合在哪里添加数据的:

public class Main {
private static boolean processFileBytes(String name, long lastModified, byte[] bytes) {
boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
//blablabla…
} else if (isClassesDex) {
synchronized (libraryDexBuffers) {
libraryDexBuffers.add(bytes);
}
return true;
} else {
//blablabla…
}
//调用processFileBytes的地方
private static class FileBytesConsumer implements ClassPathOpener.Consumer {

@Override
public boolean processFileBytes(String name, long lastModified,
byte[] bytes) {
return Main.processFileBytes(name, lastModified, bytes);
}
//blablabla…
}
//调用FileBytesConsumer的地方
private static void processOne(String pathname, FileNameFilter filter) {
ClassPathOpener opener;

opener = new ClassPathOpener(pathname, true, filter, new FileBytesConsumer());

if (opener.process()) {
updateStatus(true);
}
}
//调用processOne的地方
private static boolean processAllFiles() {
//blablabla…
// forced in main dex
for (int i = 0; i < fileNames.length; i++) {
processOne(fileNames[i], mainPassFilter);
}
//blablabla…
}
//调用processAllFiles的地方
private static int runMonoDex() throws IOException {
//blablabla…
if (!processAllFiles()) {
return 1;
}
//blablabla…
}

}

跟了一圈又跟回来了,但是注意一个变量:fileNames[i],传进去这个变量,是个地址,最终在 processFileBytes 中处理后添加到 libraryDexBuffers 中,那跟一下这个变量:

public class Main {
private static boolean processAllFiles() {
//blablabla…
String[] fileNames = args.fileNames;
//blablabla…
}
public void parse(String[] args) {
//blablabla…
}else if(parser.isArg(INPUT_LIST_OPTION + “=”)) {
File inputListFile = new File(parser.getLastValue());
try{
inputList = new ArrayList();
readPathsFromFile(inputListFile.getAbsolutePath(), inputList);
} catch(IOException e) {
System.err.println("Unable to read input list file: " + inputListFile.getName());
throw new UsageException();
}
} else {
//blablabla…
fileNames = parser.getRemaining();
if(inputList != null && !inputList.isEmpty()) {
inputList.addAll(Arrays.asList(fileNames));
fileNames = inputList.toArray(new String[inputList.size()]);
}
}

public static void main(String[] argArray) throws IOException {
Arguments arguments = new Arguments();
arguments.parse(argArray);

int result = run(arguments);
if (result != 0) {
System.exit(result);
}
}
}

跟到这里发现是传进来的参数,那我们再看看 gradle 里面传的是什么参数吧,查看 Dex task :

public class Dex extends BaseTask {
@InputFiles
Collection libraries
}
我们把这个参数打印出来:

afterEvaluate {
tasks.matching {
it.name.startsWith(‘dex’)
}.each { dx ->
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
println dx.libraries
}
}

打印出来发现是 build/intermediates/pre-dexed/ 目录里面的 jar 文件,再把 jar 文件解压发现里面就是 dex 文件了。所以 DexMerger 的工作就是合并这里的 dex 。

更改编译环境

buildscript {
//…
dependencies {
classpath ‘com.android.tools.build:gradle:2.1.0-alpha3’
}
}

将 gradle 设置为 2.1.0-alpha3 之后,在项目的 build.gradle 中即使没有设置 multiDexEnabled true 也能够编译通过,但是生成的 apk 包依旧是两个 dex ,我想的是可能为了设置 instantRun 。

解决 65536

Google MultiDex 解决方案:

在 gradle 中添加 MultiDex 的依赖:

dependencies { compile ‘com.android.support:MultiDex:1.0.0’ }

在 gradle 中配置 MultiDexEnable :

android {
buildToolsVersion “21.1.0”
defaultConfig {
// Enabling MultiDex support.
MultiDexEnabled true
}
}

在 AndroidManifest.xml 的 application 中声明:

如果有自己的 Application 了,让其继承于 MultiDexApplication 。

如果继承了其他的 Application ,那么可以重写 attachBaseContext(Context):

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

LinearAlloc

gradle:

afterEvaluate {
tasks.matching {
it.name.startsWith(‘dex’)
}.each { dx ->
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
dx.additionalParameters += ‘–set-max-idx-number=48000’
}
}

–set-max-idx-number= 用于控制每一个 dex 的最大方法个数。

这个参数在查看 dx.jar 找到:

//blablabla…
} else if (parser.isArg("–set-max-idx-number=")) { // undocumented test option
maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue());
} else if(parser.isArg(INPUT_LIST_OPTION + “=”)) {
//blablabla…

更多细节可以查看源码:Github – platform_dalvik/Main

FB 的工程师们曾经还想到过直接修改 LinearAlloc 的大小,比如从 5M 修改到 8M: Under the Hood: Dalvik patch for Facebook for Android 。

dexopt && dex2oat

dexopt

当 Android 系统安装一个应用的时候,有一步是对 Dex 进行优化,这个过程有一个专门的工具来处理,叫 DexOpt。DexOpt 是在第一次加载 Dex 文件的时候执行的,将 dex 的依赖库文件和一些辅助数据打包成 odex 文件,即 Optimised Dex,存放在 cache/dalvik_cache 目录下。保存格式为 apk路径 @ apk名 @ classes.dex 。执行 ODEX 的效率会比直接执行 Dex 文件的效率要高很多。

dex2oat

Android Runtime 的 dex2oat 是将 dex 文件编译成 oat 文件。而 oat 文件是 elf 文件,是可以在本地执行的文件,而 Android Runtime 替换掉了虚拟机读取的字节码转而用本地可执行代码,这就被叫做 AOT(ahead-of-time)。dex2oat 对所有 apk 进行编译并保存在 dalvik-cache 目录里。PackageManagerService 会持续扫描安装目录,如果有新的 App 安装则马上调用 dex2oat 进行编译。

NoClassDefFoundError

现在 INSTALL_FAILED_DEXOPT 问题是解决了,但是有时候编译完运行的时候一打开 App 就 crash 了,查看 log 发现是某个类找不到引用。

  • Build Tool 是如何分包的
    为什么会这样呢?是因为 build-tool 在分包的时候只判断了直接引用类。什么是直接引用类呢?举个栗子:

public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
DirectReferenceClass test = new DirectReferenceClass();
}
}

public class DirectReferenceClass {
public DirectReferenceClass() {
InDirectReferenceClass test = new InDirectReferenceClass();
}
}

public class InDirectReferenceClass {
public InDirectReferenceClass() {

}
}

上面有 MainActivity、DirectReferenceClass 、InDirectReferenceClass 三个类,其中 DirectReferenceClass 是 MainActivity 的直接引用类,InDirectReferenceClass 是 DirectReferenceClass 的直接引用类。而 InDirectReferenceClass 是 MainActivity 的间接引用类(即直接引用类的所有直接引用类)。

如果我们代码是这样写的:

public class HelloMultiDexApplication extends Application {
renceClass {
public DirectReferenceClass() {
InDirectReferenceClass test = new InDirectReferenceClass();
}
}

public class InDirectReferenceClass {
public InDirectReferenceClass() {

}
}

上面有 MainActivity、DirectReferenceClass 、InDirectReferenceClass 三个类,其中 DirectReferenceClass 是 MainActivity 的直接引用类,InDirectReferenceClass 是 DirectReferenceClass 的直接引用类。而 InDirectReferenceClass 是 MainActivity 的间接引用类(即直接引用类的所有直接引用类)。

如果我们代码是这样写的:

public class HelloMultiDexApplication extends Application {

Android-Dex分包最全总结:含Facebook解决方案,移动app开发相关推荐

  1. Android dex分包方案 (多dex)

    原文地址: http://my.oschina.net/853294317/blog/308583 当一个app的功能越来越复杂,代码量越来越多,也许有一天便会突然遇到下列现象: 1. 生成的apk在 ...

  2. Android图片加载框架最全解析(一),app开发入门教程

    首先,调用Glide.with()方法用于创建一个加载图片的实例.with()方法可以接收Context.Activity或者Fragment类型的参数.也就是说我们选择的范围非常广,不管是在Acti ...

  3. Android微信分享图片按质量压缩的解决方案,androidndk开发教程

    三.将io流转为byte数组 public static byte[] inputStreamToByte(InputStream is) { try { ByteArrayOutputStream ...

  4. Android修改情景模式的默认值,移动端app开发流程

    [SOLUTION] 一,针对每个情景模式的默认值修改: 1,默认铃声是统一在alps/build/target/product/core.mk文件中设置,其中ro.config.notificati ...

  5. dex分包方案概述与multidex包的配置使用

    参考资料: Android dex分包方案 Android分包MultiDex原理 <Android开发艺术探索> 博客中间会涉及到dex文件的反编译,参考博文: dex文件的反编译-de ...

  6. Android Multidex(dex分包)

    Android Multidex(dex分包) 分包: 一个dex分成多个dex 什么要分包 单个 Dalvik Executable (DEX) 中, 可调用的最大的引用总数为 65536 ,若超过 ...

  7. 【Android 热修复】热修复原理 ( 多 Dex 打包机制 | 多 Dex 支持 | Dex 分包设置 | 开发和产品风格设置 | 源码资源 )

    文章目录 一.Dex 打包设置 1.多 Dex 支持 2.Dex 分包设置 3.开发和产品风格设置 ( 非必须 ) 二.完整 build.gradle 配置 1.build.gradle 配置 2.d ...

  8. android 蓝牙传输分包,彻底掌握Android多分包技术(一)

    原标题:彻底掌握Android多分包技术MultiDex-用Ant和Gradle分别构建(一) Andrid多分包技术在大型项目编译方面起着至关重要的作用,作为一个高级开发者我们有必要掌握此技能,现在 ...

  9. 美团Android DEX自动拆包及动态加载简介

    概述 作为一个android开发者,在开发应用时,随着业务规模发展到一定程度,不断地加入新功能.添加新的类库,代码在急剧的膨胀,相应的apk包的大小也急剧增加, 那么终有一天,你会不幸遇到这个错误: ...

最新文章

  1. python网络爬虫---selenium的使用
  2. (转) Java多线程同步与异步
  3. 宽字符编码和解码通用类[CodeWidthChartUtility]
  4. linux 敏感标记 权限,闲话Linux系统安全(二)——强制访问控制(MAC)
  5. linux nfs mysql_MySQL实现高可用+共享存储NFS
  6. 又被ESLint 调戏了!!! ESLint:Newline required at end of file but not found. eslint(eol-last) [12, 22]
  7. 重启iis与mysql服务器吗_每晚定时重启IIS和数据库服务可节省服务器资源
  8. oracle中同义词的用法,Oracle中使用同义词
  9. anylogic和java_Anylogic各个版本的功能对比
  10. DeepFaceLab 新手入门教程
  11. 宇视项目VM相关笔记
  12. WEB渗透测试之三大漏扫神器
  13. android MVX杂谈
  14. 查找相交链表相交节点
  15. a pubhub service
  16. 如何找回电脑回收站删除的文件, 10种恢复工具方法!
  17. [学习笔记]省选(算法?数据结构?)·线性基
  18. Word插入题注快捷键
  19. js两数相乘出现多小数
  20. ssm毕设项目快递代收系统00pay(java+VUE+Mybatis+Maven+Mysql+sprnig)

热门文章

  1. bam获取序列_Jbrowse安装和序列、bam、vcf配置
  2. Vulkan学习(七): Swap Chain Recreation
  3. python清空字典保留变量方法,python关于字典的常用方法
  4. 路平石模具铺设路缘石公路项目质量提升的过程
  5. matplotlib绘制学术论文插图字体问题
  6. 文本分析 | 年报转换TXT关键词频统计
  7. js reduce()
  8. win10 ubuntu双系统进入系统的时候recovering journal的解决办法
  9. 矩阵计算 Armadillo Eigen Matcom
  10. 常见的100个推广创意