当程序越来越大之后,出现了一个 dex 包装不下的情况,通过 MultiDex 的方法解决了这个问题,但是在低端机器上又出现了 INSTALL_FAILED_DEXOPT 的情况,那再解决这个问题吧。等解决完这个问题之后,发现需要填的坑越来越多了,文章讲的是我在分包处理中填的坑,比如 65536、LinearAlloc、NoClassDefFoundError等等。
INSTALL_FAILED_DEXOPT
INSTALL_FAILED_DEXOPT 出现的原因大部分都是两种,一种是 65536 了,另外一种是 LinearAlloc 太小了。两者的限制不同,但是原因却是相似,那就是App太大了,导致没办法安装到手机上。
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.DexMerger$6.updateIndex(DexMerger.java:501)
at com.android.dx.merge.DexMerger$IdMerger.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) {