Android 多Dex分包机制

问题引入

随着项目工程越来越庞大,代码的方法数不断增长到一定程度,就出现Android 低版本系统应用无法安装的情况。那么这是哪里出错了?Android系统对安装包有哪些限制?
前一阵子,我们发现公司的某一个业务,在Android 2.3及系统安装不了。此时,我们该业务的Android客户端开发已经有50个人。一般外面公司的Android开发也就2~3个,代码的体量也很难增长到像我们这样的规模。但任何一个大项目都会碰到该问题。
我们来看一下,出现问题时,开发工具会有如下的提示:

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

或是

trouble writing output: Too many field references:131000; max is65536.
You may tryusing–multi-dex option.

上述两个错误提示不一样,但问题都是同一个问题。只是开发工具的编译系统版本不同,提示不同。可以看出,是某一项达到系统规定的阀值,导致安装失败了。
问题定位
从上述的提示来看,里面有一个数字65536很抢眼,这个正好是Java int的最大值。由于Android代码是以dex形式的存在,那么dex在系统里是如何定义的呢?

首先,每一个Dex文件,都会有这么一个头部信息DexHeader:
从上面可以看出,在头部信息里面分别定义了filed(字段)、method(方法)、classDefs(类)等的数量,这个数量是u4类型。至于u4类型的定义是

可以从上面的定义看出,u4类型就是uint32_t,而uint32_t正好是无符号整型2^32=65536。因此,这也就不难解释,为什么dex文件超过这个限制后,就出现无法安装的情况。

一般情况下,dex的方法数最多,因此很容易达到65536这个数量限制,而字段和类则比较难。所以,出现问题的都是方法超出居多。

Dex引用构成

一个Dex文件,它的引用组要由下面三部分组成。
因此,dex引用的方法数不仅仅和自己编写的代码有关,还和引用系统的方法数、第三方集成库的方法数有关。因此,方法数超标,也是多方面的原因构成的。
想看自己dex文件的方法数,可以搜索dex-method-counts获得自己dex数量。

解决思路

从上面的分析可以看出,出问题的原因是因为单个Dex引用的方法数超标了。那么我们是否可以拆分多个dex,从而避免单个dex的方法数超标?答案当然是可以的。
目前,为了解决dex方法数超标的问题,有三种主流思路:代码瘦身、插件化和分dex。这三种解决方案可以结合使用,效果更好。

代码瘦身

代码瘦身,可以从自己定义/引用的方法和第三方引入的库下手。

通过review自己的代码,减少方法数的定义(去掉一行函数,比如get和set,一般都是一行的,可以直接对变量做引用)、合并方法、减少对外部的依赖,从而减少最终dex引用的限制。

第三方引用库,一般一个引用jar包里面,有很多方法其实我们是引用不到的,我们可以对这些jar包做瘦身(jar是压缩包,里面都是class文件,我们去掉一些不相关的class,从而减少jar包大小),从而减少最终dex的引用限制。

同时,可以引入Proguard工具,开启Proguard的代码瘦身功能,会自动帮你删除无用的代码,最终生成的dex文件也会变小的。

插件化

插件化,也就是对程序中独立的模块做成插件,从而减少主dex文件的大小。插件化的思路其实就是对dex做拆分,从而使主dex变得更小,插件则是以独立的dex存在。

但插件化需要自己开发一套插件化的框架,成本较高,而且只有独立的模块才适合做插件。很多时候,我们的代码存在很多引用,很难拆成独立的模块。

因此,插件化,是无法从根本上解决这个问题。

分Dex

上述方案都不能从根本上解决问题,可以采用分dex的方案,适用范围更广。

分dex是将dex分成多个dex,从而避免单个dex的引用超过限制,分dex的方案不需要关心独立性问题,而且Android Studio开发工具已经支持这项能力,使用起来,成本也很低。

分Dex实现

Gradle的Android插件,从SDK build tools 21.1或是更高的版本就支持多dex的能力,需要自己手动配置一下。步骤如下:

5.1开启分包功能
在build.gradle编译的配置文件里面引入分包依赖库和开启分包功能。如下所示:

        android {
    compileSdkVersion 21
    buildToolsVersion "21.1.0"

    defaultConfig {
        ...
        minSdkVersion 14
        targetSdkVersion 21
        ...

        // Enabling multidex support.
        multiDexEnabled true
    }
    ...
}

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

2修改Application入口

一种方案是,直接修改AndroidManifest,使用MultiDexApplication,如下所示:

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

另一种方案, 如果自己有自定义的Application,则可以重写attachBaseContext()方法,在里面调用MultiDex.install(this) 来开启分dex功能。

当所有的配置OK后,Android编译工具会自动生成一个主dex(classes.dex),和其它附属dex(classes2.dex,classes3.dex)。当然这个附属dex是根据需要来会生成。

分Dex局限性

虽然分Dex,可以解决dex方法数限制问题,但开发必须关注以下问题:

  • 1附属包过大

    附属包如果过大,可能导致应用启动时发生ANR。所以,需要使用Proguard来做代码瘦身,减少附属包的大小。

  • 2低版本运行问题

    在Android 4.0以下(API level 14以下),可能出现运行不起来的问题,原因是Dalvik linearAlloc的bug。同样通过proguard可以瘦身代码,避免这个问题。

  • 3内存消耗问题

    使用多dex方案,会导致应用请求更多的内存空间,从而出现crash。原因同样来自Dalvik的linearAlloc的内存分配限制。虽然在Android 4.0上已经提高了内存分配限制,但仍然还是很有可能达到这个限制。

  • 4代码分包的复杂性
    虽然,分包解决了dex引用限制的问题,但是由于dex内部复杂的引用,所以,在对代码分包时,必须考虑到启动时就需要用到的,都必须放到主dex中。

目前编译开发工具还不支持指定class必须放到主dex中,后续开发工具会逐步完善并支持该功能。当然,如果你觉得有必要控制哪些代码必须在主dex里面,可以自己编写编译脚本。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值