构建具有超过65K个方法的Apps

北城以北 提交于 2019-12-18 19:58:09

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

由于Android平台的持续成长,Android apps的大小也一样不断变大。当你的应用程序及其引用的库达到某个大小时,你将遇到一个 表示你的app已经达到了Android app构建架构的一个限制 的build errors。早些时候的构建系统将报出一个类似下面这样的一个error:

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

更近一些的Android构建系统版本则显示一个不同的error,但指示了相同的问题:

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

从这两个errors中可以看到一个共同的数字:65,536。这个数字很重要,它表示一个单独的Davlik Executable(dex)字节码文件中的代码可以调用的引用的总个数。如果你在构建一个Android app且遇到了这个error,那么恭喜你,你的代码量非常大!这份文档解释了要如何绕过这个限制并继续构建你的app。

注意:这份文档中提供的指南取代了在Android Developers blog post Custom Class Loading in Dalvik中给出的指南。

关于65K的引用限制

Android应用程序(APK)文件以Dalvik Executable(DEX)文件的格式包含了可执行的字节码文件,而DEX包含了用于运行你的app的已编译代码。Dalvik Executable规范限制了一个单独的DEX文件内可被引用的方法的总数为65,536,包括Android framework方法,库方法和你自己的代码中的方法。要绕过这个限制需要你配置你的app的构建过程来产生多个DEX文件,即所谓的multidex配置。

Android 5.0之前multidex支持

Android 5.0之前版本的平台使用Dalvik runtime来执行app代码。默认情况下,Dalvik限制了apps为每个APK一个单独的classes.dex字节码。为了绕过这个限制,你可以使用multidex support library,它们是你的app的主DEX文件的一部分,来管理对于它们额外包含的代码和DEX文件的访问。

Android 5.0及更高版本的multidex支持

Android 5.0及更高的版本使用了一个称为ART的runtime,它原生支持由应用程序APK文件加载多个dex文件。ART在应用程序安装时执行预编译,则安装时会扫描classes(..N).dex文件并把它们编译为一个单独.oat文件给Android设备执行。关于Android 5.0 runtime的更多信息,请参考Introducing ART

避免65K限制

在配置你的app以启用对 65K或更多方法引用 的使用之前,你应该执行一些步骤来减少你的app代码调用的引用的总个数,包括你的app代码定义的方法及库。下面的做法可以帮你避免打到dex引用限制:

  • Review你的app的直接和间接依赖 - 确保你的app中包含的对大library的使用,比直接在应用程序中加代码更有价值。一个常见的不好的做法是,包含了一个非常大的库,却仅仅为了使用其中的几个utility方法。减少你的app代码的依赖常常可以帮你避免dex引用限制。
  • 用ProGuard移除无用的代码 - 为你的app配置ProGuard设定来运行ProGuard,并确定你已经为release builds打开了shrinking。打开shrinking可以确保你不会把没用的代码放进你的APKs。

使用这些技术可以帮你避免对构建配置做改动,同时能够在你的app中引用更多的方法。这些步骤也能减小你的APKs的大小,这一点对那些带宽成本很高的市场特别重要。

配置你的App支持Gradle的Multidex

Android SDK Build Tools 21.1及更高版本中的Gradle Android插件支持把multidex作为你的构建配置的一部分。在试着配置你的app支持multidex之前,请确认你已经使用SDK Manager把Android SDK Build Tools工具和Android Support Repository更新到了最新版。

要设置你的app开发工程使用multidex配置,需要你对你的app开发工程做一些修改。特别地你需要执行下面的这些步骤:

  • 修改你的Gradle构建配置以启用multidex。

修改你的app Gradle构建文件配置包含support library并启用multidex输出,如下面的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'
}

注意:你可以在Gradle构建文件的defaultConfigbuildType,或productFlavor段中指定multiDexEnabled设定。

在你的manifest中向你的application元素添加来自于multidex support library的MultiDexApplication

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

给app添加了这些配置设定之后,Android构建工具会构建一个主dex(classes.dex),并在需要时构建一些supporting(classes2.dex,classes3.dex)。构建系统将把它们打包进一个APK以用于发布。

注意:如果你的app继承了Application类,你可以覆写attachBaseContext()方法并调用MultiDex.install(this)以启用multidex。要了解更多信息,请查看MultiDexApplication参考文档。

multidex support library的限制

multidex support library有一些已知的限制,你应该对它们有所意识,并在把它合进你的app构建配置时做一些针对行的测试:

  • 在启动期间将.dex文件安装到设备的data分区是很复杂的,如果次要的dex文件非常大的话,这可能导致Application Not Responding (ANR) errors。在这种情况下,你应该应用ProGuard的代码shrinking技术来最小化dex文件的大小,并移除无用的代码。
  • 使用了multidex的应用程序,在Android4.0(API level 14)之前版本平台的设备上,可能由于一个Dalvik linearAlloc bug (Issue 22586)而无法启动。如果你的目标API levels早于14,则请确保针对这些版本的平台执行了测试,你的应用程序能够启动,且特定的group of classes能够被加载。代码shrinking可以减少或可能消除这些潜在的问题。
  • 使用了multidex配置的应用程序,在运行期间请求大量的内存分配时,可能由于一个Dalvik linearAlloc限制 (Issue 78035)而crash。Android 4.0 (API level 14)中分配限制增长了,但在Android 5.0 (API level 21) 之前的Android版本上app仍然可能遇到这个限制。
  • 关于Dalvik runtime执行时主dex中需要什么类 的要求(requirements)非常复杂。Android构建工具更新处理了Android的要求(requirements),但包含的库可能有一些额外的依赖要求 (requirements),这包括使用introspection,或在native代码中调用Java方法。在multidex构建工具被更新以允许你指定必须被包含进主dex文件的类之前有些库可能无法使用

优化Multidex开发构建

由于构建系统必须做 关于什么类必须被放在主DEX文件而什么类可以被放入次要DEX文件 的复杂决定,一个multidex配置请求 可能会使构建过程的时间大为增加。这意味着作为开发过程一部分而执行的构建例程,在具有multidex时,将消耗更多时间,并可能降低开发过程的速度。

为了延缓multidex输出造成的构建时间增长,你应该使用Gradle Android插件的productFlavors,在你的构建输出上创建两个variantions:一个开发flavor和一个产品flavor。

对于开发flavor,设置最小SDK版本为21。这种设定将使用ART支持的格式产生multidex输出,这要快得多。对于release flavor,设置最小SDK版本为你实际要支持的最小版本。这个设定产生一个multidex APK,它能与更多的设备兼容,但构建也会耗费更多的时间。

下面的构建配置示例演示了如何在一个Gradle构建文件中设置这些flavors:

android {
    productFlavors {
        // Define separate dev and prod product flavors.
        dev {
            // dev utilizes minSDKVersion = 21 to allow the Android gradle plugin
            // to pre-dex each module and produce an APK that can be tested on
            // Android Lollipop without time consuming dex merging processes.
            minSdkVersion 21
        }
        prod {
            // The actual minSdkVersion for the application.
            minSdkVersion 14
        }
    }
          ...
    buildTypes {
        release {
            runProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                                                 'proguard-rules.pro'
        }
    }
}
dependencies {
  compile 'com.android.support:multidex:1.0.0'
}

在完成了这个配置改动之后,你可以使用你的app的devDebug variant,它结合了dev productFlavor和debug buildType的属性。使用这个target将创建一个debug app,其中禁掉了proguard,启用了multidex,minSdkVersion被设置为了Android API level 21。这些设定将使得Android gradle插件做下面的事情:

  1. 把应用程序的每个模块 (包括依赖) 构建为不同的dex文件。这通常被称为pre-dexing。
  2. 不加修改地将每个dex文件包含进APK。
  3. 最重要的是,模块dex文件将不会被结合 (combined),从而耗费大量时间来计算哪些内容要被放进主dex的过程就被省略了。

由于只需要对修改了的模块的dex文件进行重新计算并打包进APK,这些设定导致了快速的,增量构建。这些构建的结果APK只能用于Android 5.0设备上的测试。然而,通过将配置实现为一个flavor,你将保有为release执行普通的构建的能力 - 适当的最小SDK level及proguard设定。

你也可以构建其它的variants,包括prodDebug variant build,它需要花费更多的时间来构建,但可被用于开发之外的测试。在展示的配置中,prodRelease variant将是最终的测试和发布版。如果你在通过命令行执行gradle tasks,你可以使用在最后附加了DevDebug标准命令 (比如./gradlew installDevDebug)。更多关于使用Gradle tasks的flavors的信息,请查看Gradle Plugin User Guide

提示:你也可以为每个flavor提供一个定制的manifest,或一个定制的application,这使你可以使用support library MultiDexApplication类,或只在需要的variant中调用MultiDex.install() 。

在Android Studio中使用Build Variants

当使用multidex时,对于管理构建过程来说,build variants可能非常有用。Android Studio允许你在UI中选择这些build variants

Android Studio构建你的app的"devDebug" variant:

  1. 在左边栏中打开Build Variants窗口。这个选项挨着Favorites
  2. 点击build variant的名字并选择一个不同的variant,如图1所示的那样。

图1. Android Studio中左边面板显示一个build variant的截图

注意:打开这个窗口的选项只有在你已经使用命令Tools > Android > Sync Project with Gradle Files成功地同步了Android Studio和你的Gradle文件之后才可用。

测试Multidex Apps

当使用instrumentation测试multidex apps时,需要一些额外的配置来启用test instrumentation。由于在multidex apps中,类的代码的位置不是在一个单独的DEX文件中的,则除非针对multidex做了配置,否则instrumentation tests无法适当地运行。

要用instrumentation tests测试一个multidex app,则配置multidex testing support library的MultiDexTestRunner。下面的示例build.gradle文件演示了如何配置你的构建来使用这个test runner:

android {
  defaultConfig {
      ...
      testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
  }
}

注意:使用Gradle版本低于1.1的Android Plugin时,你需要为multidex-instrumentation添加下面的依赖:

dependencies {
    androidTestCompile('com.android.support:multidex-instrumentation:1.0.1') {
         exclude group: 'com.android.support', module: 'multidex'
    }       
}

你可能直接使用instrumentation test runner类或继承它来适应你的测试需要。或者你可以像下面这样在现有的instrumentations中覆写onCreate:

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

注意:现在还不支持使用multidex来创建一个测试APK。

Done。

原文

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!