64K引用限制

前言

安卓应用大小随安卓平台持续成长在增加。应用及其引用库达特定大小遇构建错误,指明应用已达安卓应用构建架构极限。早版构建系统报错如下:

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

新版构建系统虽提示不同,但指问题相同:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

这些错误状况显示数65536。该数很重要,代表单Dalvik Executable(DEX)字节码文件内代码可调用引用总数。本文介绍如何通过启用被称为Dalvik可执行文件分包的应用配置越过该限制,使应用构建并读取Dalvik可执行文件分包DEX文件。

关于64K引用限制

安卓应用(APK)文件包含Dalvik Executable(DEX)文件形式可执行字节码文件,其中包含运行应用已编译代码。Dalvik Executable规范使在单DEX文件内可引用方法总数限制在65536,其中包括安卓框架方法、库方法及代码方法。计算机科学领域内术语千(简称 K)表1024(2^10)。65,536 等64 X 1024,该限制亦称“64K引用限制”。

安卓5.0前版Dalvik可执行文件分包支持

安卓5.0(API 21)前版用Dalvik运行时执行应用代码。默认情况Dalvik限制应用每APK用单classes.dex字节码文件。绕过该限制用Dalvik可执行文件分包支持库,它成应用主DEX文件一部分,然后管理对其它DEX文件及其所包含代码的访问。

注:项目配置面向Dalvik可执行文件分包所用minSdkVersion 20或更低且将其部署到安卓4.4(API 20)或更低目标设备则Android Studio停用Instant Run

安卓5.0及更高版Dalvik可执行文件分包支持

安卓5.0(API 21)及更高版用名为ART的运行时,后者原生支持从APK文件加载多DEX文件。ART在应用安装时执行预编译,扫描classesN.dex文件并将其编译成单.oat文件供安卓设备执行。故minSdkVersion为21或更高则不需Dalvik可执行文件分包支持库。

了解安卓5.0运行时详细信息参阅ART 和 Dalvik

注:应用minSdkVersion设21或更高,用Instant Run时Android Studio自动将应用配置为进行Dalvik可执行文件分包。因Instant Run仅适用调试版应用,故仍需配置发布构建进行Dalvik可执行文件分包以规避64K限制。

规避64K限制

在将应用配置为支持使用64K或更多方法引用前应采取措施减少应用代码调用的引用总数,包括由应用代码或包含的库定义的方法。下列策略可避免达DEX引用限制。

  • 检查应用直接和传递依赖项,确保应用中用任何庞大依赖库好处大于为应用添大量代码之弊端。一种常见反面模式仅为用几个实用方法而在应用中加非常庞大库。减少应用代码依赖项往往能帮助规避dex引用限制。
  • 通过ProGuard移除未使用代码,为版本构建启用代码压缩以运行ProGuard。启用压缩可确保交付APK不含未使用代码。

这些技巧避免在应用中启用Dalvik可执行文件分包且减小APK总体大小。

配置您的应用进行 Dalvik 可执行文件分包

将应用项目设为用Dalvik可执行文件分包配置需应用项目进行如下修改,具体取决于应用所支持最低安卓版本。
minSdkVersion设21或更高只需在模块级build.gradle中multiDexEnabled设true:

android {
    defaultConfig {
        ...
        minSdkVersion 21 
        targetSdkVersion 26
        multiDexEnabled true
    }
    ...
}

minSdkVersion设20或更低须按如下方式用Dalvik可执行文件分包支持库

  • 修改模块级build.gradle以启用Dalvik可执行文件分包并将Dalvik可执行文件分包库添加为依赖项:
    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 26
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
      compile 'com.android.support:multidex:1.0.1'
    }
    
  • 据是否替换Application类执行以下操作之一:
    • 没替换Application类编辑清单文件,按如下方式设标记中android:name:

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.myapp">
          <application
                  android:name="android.support.multidex.MultiDexApplication" >
              ...
          </application>
      </manifest>
      
    • 替换Application类按如下方式对其进行更改以扩展MultiDexApplication(如果可能):

      public class MyApplication extends MultiDexApplication { ... }
      
    • 替换Application类但无法更改基本类,则可改为替换attachBaseContext()方法并调用MultiDex.install(this)启用Dalvik可执行文件分包:

      public class MyApplication extends SomeOtherApplication {
        @Override
        protected void attachBaseContext(Context base) {
           super.attachBaseContext(context);
           Multidex.install(this);
        }
      }
      

构建应用后安卓构建工具据需构建主DEX文件(classes.dex)和辅助DEX文件(classes2.dex、classes3.dex 等)。然后构建系统将所有DEX文件打包到APK中。

运行时Dalvik可执行文件分包API用特殊类加载器搜索适用于方法的所有DEX文件(而非仅在主classes.dex 文件搜索)。

Dalvik可执行文件分包支持库局限性

Dalvik可执行文件分包支持库具有一些已知局限性,将其纳入应用构建配置中应注意这些局限性并进行针对性测试:

  • 启动期间在设备数据分区中安装DEX文件过程相当复杂,辅助DEX文件较大可能致应用无响应 (ANR) 错误。此情况下应通过ProGuard应用代码压缩尽量减小DEX文件大小并移除未用代码。

  • 因存在Dalvik linearAlloc错误(问题 22586),用Dalvik可执行文件分包应用可能无法在早于Android 4.0(API 14)运行平台版本设备启动。目标API级别低于14务必针对这些版本平台进行测试,因应用可能在启动或加载特定类群出问题。代码压缩可减少甚至有可能消除这些潜在问题。

  • 因存在Dalvik linearAlloc限制(问题 78035),因此用Dalvik可执行文件分包配置应用发出非常庞大内存分配请求,可能在运行期发生崩溃。尽管Android 4.0(API 14)提高分配限制,但Android 5.0(API 21)前应用仍可能遭遇该限制。

声明主DEX文件所需类

为Dalvik可执行文件分包构建每DEX文件时,构建工具执行复杂决策制定来确定主要DEX文件所需类,以便应用能够成功启动。启动期需要的任何类未在主DEX文件提供,那么应用将崩溃并出错误 java.lang.NoClassDefFoundError

该情况不应出现在直接从应用代码访问的代码上,因构建工具能识别这些代码路径,但可能在代码路径可见性较低(如所用库具有复杂依赖项)时出现。例如,若代码用自检机制或从原生代码调用Java方法,那么这些类可能不会被识别为主DEX文件必需项。

因此收到java.lang.NoClassDefFoundError须用构建类型中multiDexKeepFilemultiDexKeepProguard属性声明它们以手动将这些其他类指定为主DEX文件必需项。类在 multiDexKeepFilemultiDexKeepProguard文件中匹配则该类会添至主DEX文件。

multiDexKeepFile属性

multiDexKeepFile中指定的文件应每行包含一类且用com/example/MyClass.class 格式。例如可创建一名为multidex-config.txt文件,如下所示:

com/example/MyClass.class
com/example/MyOtherClass.class

然后可按以下方式针对构建类型声明该文件:

android {
    buildTypes {
        release {
            multiDexKeepFile file 'multidex-config.txt'
            ...
        }
    }
}

Gradle会读取相对于build.gradle文件的路径,multidex-config.txtbuild.gradle文件在同一目录则以上示例有效。

multiDexKeepProguard 属性

multiDexKeepProguard文件使用与Proguard格式相同且支持整Proguard 语法。了解有关Proguard格式和语法详细信息参阅Proguard手册中Keep Options一节。

multiDexKeepProguard中指定文件应在任何有效ProGuard语法中包含-keep选项。例如-keep com.example.MyClass.class。可创建一名为multidex-config.pro文件,如下所示:

-keep class com.example.MyClass
-keep class com.example.MyClassToo

指定包中所有类,文件将如下所示:

-keep class com.example.** { *; }// All classes in the com.example package

然后可按以下方式针对构建类型声明该文件:

android {
    buildTypes {
        release {
            multiDexKeepProguard 'multidex-config.pro'
            ...
        }
    }
}

优化开发构建中的 Dalvik 可执行文件分包

Dalvik可执行文件分包配置会大幅增加构建处理时间,因构建系统须就哪些类必须包括在主DEX文件中及哪些类可包括在辅助DEX文件中作出复杂决策。这意味着用Dalvik可执行文件分包增量式构建通常耗时更长,可能会拖慢开发进度。

为缩短耗时更长的Dalvik可执行文件分包输出构建时间,请利用productFlavors(一开发定制和一发布定制,具有不同minSdkVersion值)创建两构建变型。

开发定制将minSdkVersion设21。该设置将启用一名为pre-dexing构建功能,此功能使用仅适用于 Android 5.0(API 21)和更高版ART格式更快生成Dalvik可执行文件分包输出。发布定制将 minSdkVersion设适于实际最低支持级别。此设置生成Dalvik可执行文件分包APK可兼容更多设备,但构建时间更长。

以下构建配置示例展示如何在Gradle构建文件设这些定制:

android {
    defaultConfig {
        ...
        multiDexEnabled true
    }
    productFlavors {
        dev {
            // Enable pre-dexing to produce an APK that can be tested on
            // Android 5.0+ without the time-consuming DEX build processes.
            minSdkVersion 21
        }
        prod {
            // The actual minSdkVersion for the production version.
            minSdkVersion 14
        }
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    compile 'com.android.support:multidex:1.0.1'
}

完成此配置变更后可为增量式构建使用应用devDebug变体,后者集dev产品定制与debug构建类型属性于一身。这将创建已启用Dalvik可执行文件分包且禁用proguard可调试应用(因minifyEnabled默false)。这些设置使适用于Gradle的Android插件执行以下操作:

  • 执行pre-dexing,将每应用模块和每依赖项构建为单独DEX文件。
  • 将每DEX文件加入APK且不做任何修改(不执行代码压缩)。
  • 最重要,模块DEX文件不执行合并操作,因此可避免为确定主DEX文件内容而进行长时间计算。
    这些设置好处是,可进行快速增量式构建,因仅修改过模块DEX文件才会在后续构建期重计算并重打包。但这些构建的APK仅用于在Android 5.0设备测试。不过因以定制形式实现配置,故保留了使用与发布相适的最低API级别和ProGuard代码压缩执行正常构建能力。

还可构建其它变体,包括prodDebug变体构建,该变体虽构建时间更长,但可用于开发以外测试。在所示配置内,prodRelease变体将是最终测试和发布版本。了解有关使用构建变体的详细信息参阅配置构建变体

提示:因有适用于不同Dalvik可执行文件分包需求的不同构建变体,因此可为不同变体提供不同清单文件(这样仅适用于API级别20和更低版本清单文件会更改标记名称)或为每变体创建不同的Application子类(这样仅适用于API级别20和更低版本清单文件会扩展MultiDexApplication类或调用 MultiDex.install(this))。

测试Dalvik可执行文件分包应用

编写面向Dalvik可执行文件分包应用仪器测试无需进行其它配置。AndroidJUnitRunner直接支持Dalvik可执行文件分包,前提用MultiDexApplication或替换自定义Application对象中attachBaseContext()方法并调用MultiDex.install(this)以启用Dalvik可执行文件分包。

或可替换AndroidJUnitRunner中onCreate()方法:

public void onCreate(Bundle arguments) {
    MultiDex.install(getTargetContext());
    super.onCreate(arguments);
    ...
}

注:目前不支持用Dalvik可执行文件分包创建测试APK。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

snpmyn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值