Android Dex分包之旅

当程序越来越大之后,出现了一个 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) {
            resu
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值