阅读 938

Android Gradle 常用配置

Android Gradle 提供了大量的 DSL 给我们,以方便我们根据自己的需求定义相应的配置。

在这里记录一些常用的配置,以方便使用的时候查询。老话说 好记性不如烂笔头。

关于 Android 项目的配置几乎全部在 android{} 里了,我这里记录的也全是 android 扩展里的配置。

最上面的两个配置

//编译的SDK 版本
compileSdkVersion 29
//构建工具版本
buildToolsVersion "29.0.2"
复制代码

defaultConfig 默认配置

defaultConfig{} 是一个配置块,负责定义所有的默认配置。

如果一个变体没有定义自己的配置,就会默认使用 defaultConfig{} 里的配置。

例如 应用 ID ,版本号,版本名等。

//默认配置
defaultConfig {
    //应用程序ID,创建时的包名,可以更改。
    applicationId "com.skymxc.example"
    //最小支持的SDK 版本
    minSdkVersion 19
    //目标 SDK 版本
    targetSdkVersion 29
    //应用版本代码,一般用于控制APP的升级。
    versionCode 1
    //应用版本名称,用户可以看到。
    versionName "1.0"

    //配置单元测试使用的 runner
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
复制代码

如果是 library 项目,是没有 applicatoinId 的,但是有一个 consumerProguardFiles 配置

defaultConfig {
    minSdkVersion 19
    targetSdkVersion 29
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    //配置库自己的 混淆规则,打包时会被打包进 AAR 包里,在 使用这个库的项目里会自动应用库里的混淆规则。
    consumerProguardFiles 'consumer-rules.pro'
}
复制代码

构建类型

构建类型是 Gradle 在构建和打包 APK 时使用的某些配置,通常是为开发生命周期的不同阶段做配置。

例如 debug 构建类型 是在开发调试阶段使用的,Gradle 会使用 调试密钥库为其签名; release 构建类型是正式发布打包阶段使用的,会缩减 APK,对 APK 进行混淆处理并使用特定的加密密钥库。

Android studio 在创建项目时就会自动创建两个构建类型:debug 和 release 。

虽然没有把 debug 显示在配置脚本上,但会有 debuggable true 配置它。

//构建类型
buildTypes {
    //发布类型
    release {
        //是否启用混淆
        minifyEnabled false
        //proguard 规则文件;
        //getDefaultProguardFile 是 Android 扩展的一个方法,可以获取你的 Android SDK 目录下默认的 proguard 配置文件。
        //在 android-sdk/tools/proguard/目录下,文件名就是传入的 proguard-android-optimize.txt
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
复制代码

如果你有其他别的配置要在 debug 构建类型上配置,直接定义即可。

例如 我想让 调试版和发布版本都能在一个手机上安装,就可以为 debug 构建类型 增加一个配置,更改它的应用ID

//构建类型
buildTypes {
    //正式发布阶段使用
    release {
        //是否启用混淆
        minifyEnabled false
        //proguard 规则文件;
        //getDefaultProguardFile 是 Android 扩展的一个方法,可以获取你的 Android SDK 目录下默认的 proguard 配置文件。
        //在 android-sdk/tools/proguard/目录下,文件名就是传入的 proguard-android-optimize.txt
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    //调试阶段使用
    debug {
        applicationIdSuffix ".debug"
    }
}
复制代码

如果你有其他需求是这两个类型不能满足的,也可以添加一个构建类型,自定义其中的配置。

buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
            applicationIdSuffix ".debug"
            debuggable true
        }

        staging {
            initWith debug
            manifestPlaceholders = [hostName:"internal.example.com"]
            applicationIdSuffix ".debugStaging"
        }
    }
复制代码

关于构建类型可以使用的配置,我把常用的在这里列一下,方便以后查阅

  • applicationIdSuffix 基于默认 applicationId 追加后缀。(注意也要符合 applicationId 的定义规则) 接受 String 类型的值。
  • debuggable 是否生成一个可调式的 APK。接受 Boolean 类型的值。
  • jniDebuggable 是否生成一个可供调试 Jni(C/C++) 代码的 APK。接受 Boolean 类型的值。
  • minifyEnabled 是否进行代码缩减、启用 Proguard 混淆。接受 Boolean 类型的值。
  • proguardFiles 配置 Proguard 使用的规则文件,可以接受多个文件。
  • shrinkResources 是否清理未使用的资源,默认是 false 。
  • signingConfig 配置使用的签名

更多更详细的属性和方法查看文档吧,传送门

产品变种

产品变种可以理解为应用的不同版本。例如,农村版和非农版。

可以自定义变种以使用不同的代码和资源,同时共享和重复利用所有应用版本共用的部分。

产品变种是配置出来的,默认是没有的,有需要则需要自己配置。

产品变种的配置和构建类型的差不多。

  1. 首先要添加一个产品维度,可以理解为一个分组,所有的变种都必须指定一个维度
//变种维度
flavorDimensions "domicile"
复制代码
  1. 添加变种
//变种维度
flavorDimensions "domicile"
//产品变种
productFlavors{
    //农村 变种
    countryside {
        //指定所属维度
        dimension "domicile"
        versionNameSuffix '-countryside'
    }
    //城市 变种
    city {
        dimension "domicile"
        versionNameSuffix '-city'
    }
}
复制代码

defaultConfig{} 中的配置在变种里都可以用,他们两个都是属于 ProductFlavor 类

比如,想给不同变种设置不同的版本就可以像上面那样设置。

如果没有在变种中配置任何自定义属性,将使用 defaultConfig 中的配置。

在添加产品变种并同步后, Gradle 就会生成很多任务,基本上都是基于 构建类型+产品变种的方式生成的。

比如 assembleCity、assembleRelease 、assembleCityRelease 等。

assemble 开头的负责生成 APK,比如 assembleCity 运行之后就会生成 City 变种的 release 和 debug 包;

assembleRelease 运行之后就会生成所有变种的的 release 包;

而 assembleCityRelease 运行后只会生成 city 的 release 包。

除了 assemble 系列,还有 compile系列,install系列等等,可以通过命令 gradle tasks 查看。

除了生成任务之外,每个产品变种和构建类型还可以有自己的 源集、依赖。

这就意味着可以为每个变种和构建类型定义他们自己的资源、代码以及依赖的第三方库。

关于怎么为构建类型,产品变种定义自己的资源、代码和依赖下面和变体一起看。

产品变种也是多渠道构建的基础,通常多渠道构建就是配置多个变种。

构建变体

构建变体是 Gradle 使用一组特定规则将构建类型和产品变种中配置的设置、代码和资源组合在一起的结果。

它是构建类型和产品变种的交叉产物,也是 Gradle 用来构建应用的配置。

例如,“city” 产品变种可以定义不同的功能和设备要求(如自定义源代码、资源和最低 API 级别),而 “debug” 构建类型则会应用不同的构建和打包设置(如调试选项和签名密钥)。

生成的构建变体是应用的“cityDebug”版本,它由“city”产品变种、“debug”构建类型和 main/ 源代码集中包含的配置和资源组合而成。

你无法直接创建和更改变体,只能通过更改构建类型和产品变种的方式创建和配置变体。

在脚本中添加产品变种后,点击 “Sync Now” 。

同步完成后,Gradle 会根据构建类型和产品变种自动创建构建变体,并按照 <product-flavor><Build-Type> 为其命名。

例如,如果创建了“countryside”和“city”产品变种,并保留了默认的“debug”和“release”构建类型,则 Gradle 会创建以下构建变体:

  • cityDebug
  • cityRelease
  • countrysideDebug
  • countrysideRelease

变体同步出来之后,你可以任意选择要构建运行的变体,只需要在 Android studio 的 Build Variant 窗口选择即可:

buildVariants

源代码集

想知道怎么为构建变体配置特定资源,就需要先了解源代码集的概念。

源代码集俗称源集,是 Android studio 按照逻辑关系将每个模块的源代码和资源进行的分组。

模块的 main 源集包含所有构建变体共用的代码和资源。

其他的源集都是可选的,在添加构建类型,产品变种或者配置构建变体后,Android studio 并不会自动的创建对应的源集。

我们可根据实际需求自行创建:

  • src/main/ 此源代码集包含所有构建变体共用的代码和资源。

  • src/buildType/ 创建此源代码集可加入特定构建类型专用的代码和资源。

  • src/productFlavor/ 创建此源代码集可加入特定产品变种专用的代码和资源。 注意:如果配置构建以组合多个产品变种,则可以为变种维度之间的每个产品变种组合创建源代码集目录:src/productFlavor1ProductFlavor2/

  • src/productFlavorBuildType/ 创建此源代码集可加入特定构建变体专用的代码和资源。

例如,如果要生成 “cityDebug” 版本,Android Gradle 需要合并来自以下源集的代码、资源和配置:

  • src/cityDebug (构建变体源集)
  • src/debug (构建类型源集)
  • src/city (产品变种源集)
  • src/main (主源集)

如果不同源集包含同一资源的不同版本,Gradle 将按照以下优先顺序决定使用哪一个(左侧源集替换右侧源集的文件和设置)

构建变体 > 构建类型 > 产品变种 > 主源代码集 > 库依赖项
复制代码

这样一来,Gradle 在你构建某个变体时就会专门使用对应的源集,同时重复利用与应用的其他版本共用的 Activity、应用逻辑和资源。

在合并多个清单时,Gradle 会使用相同的优先顺序,这样每个构建变体都能在最终清单中定义不同的组件或权限。

前面说到为构建类型,产品变种和构建变体创建自己专用的代码和资源就是通过创建各自的源集实现的。

例如,要为 debug 构建类型创建源集,只需要在 src目录下创建一个 debug 目录就好了。

其目录结构也和 main 源集一样就好,java 代码 放在 java 目录里,资源文件文件放在 res 目录里。

debugSourceSet

为产品变种和构建变体创建源集都是一样的,根据名称创建在 src 下创建对应的目录即可。 目录里对资源和代码的组织和 main 源集一样即可。

buildVariant-sourceSet

Android Gradle 提供了一个任务 :sourceSets .

这个任务的输出展示了如何组织每个构建类型、产品变种和构建变体的文件。

sourceSet

当然这些都不是说定死了, 只不过约定俗成的都这么用,如果你偏偏不想这么用,非想特立独行的改变源集的组织方式也是可以的。自行查阅吧,关键词: “更改默认源代码集配置”

单独配置资源和代码整完了,接着看怎么单独配置依赖,使用第三方库。

在关键字 implementation 前边加上源集的名称就可以了,我们创建一个应用模块时就能看到这样的例子

// main 源集的依赖
implementation 'androidx.constraintlayout:constraintlayout:1.1.3’
//test 源集的依赖
testImplementation 'junit:junit:4.12’
//androidTest 源集的依赖
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
复制代码

为 debug 构建类型添加依赖

debugImplementation project(":library")
复制代码

为 city 构建变种添加依赖

cityImplementation project(":library")
复制代码

为 cityDebug 构建变体添加依赖

cityDebugImplementation project(":library")
复制代码

配置签名信息

APP 只有在签名之后才能被发布,安装,使用,签名是保护 APP 的一种方式,标记该 APP 的唯一性。

如果签名被恶意篡改,签名就不一样了,就无法安装升级了,一定程度上也保护了我们的 APP。

要对 APP 签名 就需要一个签名证书文件,这个文件怎么创建下面会有。

一般情况下我们有两种构建类型,debug 和 release ,对应的是开发生命周期中的两个阶段,通常也是只给发布类型的 APP 配置签名信息。

开发调试时,Android studio 已经为我们提供了一个默认的 debug 签名证书,可以直接使用,无需单独配置,一般位于 $HOME/.android/debug.keystore ;密码为 android 。

Android Gradle 提供了 signingConfigs{} 以便我们配置签名信息,只需要将签名信息添加在这个代码块里就可以了。

//签名密钥库配置

signingConfigs {
    //配置一个签名信息,名称为 release 
    release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
    }
}
复制代码

signingConfigs{} 代码块里配置实际是属于 SigningConfig 类型的,一个SigningConfig 就是一个签名配置,可以添加多个签名信息。

其中 release 就是签名配置的名字,代码块里就是对当前签名的配置。

其中常用的配置属性如下

  • storeFile 签名证书文件
  • storePassword 签名证书文件密码
  • keyAlias 密钥别名
  • keyPassword 密钥密码

更详细的配置看这里 SigningConfig

一个签名证书里可以有多个 密钥,每个密钥 都有自己的密码。

Android studio 目前是提供了图形窗口供我们使用的,在窗口里填好信息就自动配置脚本。

配置签名信息

配置完成 签名后,就可以直接在构建类型或者产品变种的配置中引用了

//城市 变种
city {
    dimension "domicile"
    versionNameSuffix '-city'
    signingConfig signingConfigs.city
}
复制代码

创建签名证书

1.在上方工具条的 build 菜单里选择 Generate Signed Builde /APK

创建签名证书

  1. 选择 APK,点击 next

    创建签名证书

  2. 选择 Create New… 进入创建窗口

创建签名证书

  1. 然后就是创建签名证书文件,选择存储路径,设置密码,创建密钥,设置密钥密码,有效期默认就好,其他的信息视自己情况配置。填写完成后,点击 OK 就可以了。

    创建签名证书

5.在创建完签名证书后,会自动赋值到之前的窗口,到这里签名证书文件就创建完成了,如果不继续生成 APK 就可以关闭了。

创建签名证书

压缩 APK

在配置了 minifyEnabled true 之后系统默认就会启用 R8 代码压缩,优化,混淆功能。

混淆规则是配置在 proguard-rules.pro 文件里,可通过 proguardFiles 配置更改。

//发布类型
release {
    //是否启用混淆
    minifyEnabled false
    //proguard 规则文件;
    //getDefaultProguardFile 是 Android 扩展的一个方法,可以获取你的 Android SDK 目录下默认的 proguard 配置文件。
    //在 android-sdk/tools/proguard/目录下,文件名就是传入的 proguard-android-optimize.txt
    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
复制代码

文件里默认是没有任何规则的,需要自行配置。

Android Gradle 在 3.4.0 以及之后的版本已经默认不使用 Proguard 了,改成 R8 编译器了。

目前 R8 编译器是 支持所有现有的 Proguard 规则文件,所以即使现在不使用 Proguard 了,仍然不需要做什么改变。

关于 Proguard 的规则我在另一篇文章里详细的解释了一下 Proguard 常用规则

R8 编译器几乎和 Proguard 编译过程差不多,在编译时会处理以下任务

  • 代码缩减(即摇树优化):从代码中检测没有使用到的类、字段、方法和属性然后进行安全的移除。这里如果你用到了反射并没有直接引用就需要配置保留入口点了。
  • 资源缩减:移除不使用的资源,包括依赖的库中的资源。如果你有动态引用的资源就需要配置哪些资源不能被清除
  • 混淆处理:缩短或者重命名类和成员的名称,从而减小 DEX 文件的大小。
  • 优化:检查并重写代码,进一步缩小 DEX 的大小。例如,如果 R8 检测到 if/else 语句中的 else {} 分之没有采用过,就会把 else {} 移除

在设置了 minifyEnabled true 之后,R8 会从多个地方读取 Proguard 规则文件,这里把 R8 使用的文件来源列一下,了解一下编译器都是从哪里读取配置文件。

来源位置说明
Android studio\/proguard-rules.pro在使用 IDE 创建新模块时,IDE 会在该模块的根目录创建 proguard-rules.pro 文件默认是没有任何规则的,需要自行配置。
Android Gradle 插件由 Android Gradle 插件在编译时生成Android Gradle 插件会生成 proguard-android-optimize.txt(其中包含了对大多数 Android 项目都有用的规则),并启用 @Keep* 注释。
默认情况下,使用 Android Studio 创建新模块时,模块级 build.gradle 文件会配置此文件。
库依赖项AAR 库:\/proguard.txt
JAR 库:\/META-INF/proguard/
如果某个 AAR 库是使用它自己的 ProGuard 规则文件发布的,并且将该 AAR 库作为编译时依赖项纳入到项目中,那么 R8 在编译项目时会自动应用其规则。(library 项目里的 consumerProguardFiles 配置)

如果 AAR 库需要某些保留规则才能正常运行,那么使用该库随附的规则文件将非常有用。
也就是说,库开发者已经为你配置好了规则。不过,请注意,由于 ProGuard 规则是累加的,因此 AAR 库依赖项包含的某些规则无法移除,并且可能会影响对应用其他部分的编译。
例如,如果某个库包含停用代码优化功能的规则,该规则会针对整个项目停用优化功能。
Android 资源打包工具 2 (AAPT2)使用 minifyEnabled true 构建项目后:\/build/intermediates/proguard-rules/debug/aapt_rules.txtAAPT2 会根据对应用清单中的类、布局及其他应用资源的引用,生成保留规则。
例如,AAPT2 会为您在应用清单中注册为入口点的每个 Activity 添加一个保留规则。
自定义配置文件默认情况下,当使用 Android Studio 创建新模块时,IDE 会创建 \/proguard-rules.pro,以便添加自己的规则。我们可以添加自定义的配置,R8编译器在编译时会应用这些配置

在配置了 minifyEnabled true 之后, R8 会将来自上述所有可用来源的配置文件组合到一起。

如果需要查看 R8 在构建项目时应用的所有规则的完整报告,可以将以下代码加入到模块的 proguard-rules.pro 文件中 路径和名字可以随意更改

-printconfiguration ~/tmp/full-r8-config.txt
复制代码

自动清理未使用的资源

在配置 minifyEnabled true 后 R8 编译器会将代码进行优化缩减混淆,那么资源呢,如何优化呢?

答案是通过配置 shrinkResources 属性

buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
        }
    }
复制代码

通过 shrinkResources true 配置 资源缩减,可将没有使用到的资源自动移除。

配置资源缩减的前提是配置了代码缩减,资源缩减只有在与代码缩减配合才能发挥作用。

在代码缩减器移除掉所有不使用代码后,资源缩减器便可以确定哪些资源没有被引用,从而安全的移除。

通常我们在实际开发中会有一些资源是未被直接引用的,存在动态引用或者反射引用的可能,这时就需要配置一些规则,告诉资源缩减器规则内的资源不能被移除。

这就和配置 Proguard 规则保留入口点的道理一样。

配置要保留的资源

如果需要自定义保留或者舍弃特定资源,需要在项目中创建一个包含 <resources> 标记的 XML 文件。

可以在 tools:keep 属性中指定要保留的资源,在 tools:discard 属性中指定要舍弃的资源。

这两个属性都接受以逗号分隔的资源名称列表,也可以使用 * 作为通配符。

例如

<resources
    xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@mipmap/more,@mipmap/saw"
    tools:discard="@mipmap/*_2"
    tools:shrinkMode="strict">
<!--保留 more saw文件-->
<!--    舍弃 share_2 文件-->
<!--    严格模式-->
</resources>
复制代码

通常将文件保存在 /res/raw/keeps.xml 。Android Gradle 不会将其打包进 APK,除非你在代码中使用了 R.raw.keeps

上面的三个属性都是可选项,其中 shrinkMode 是配置自动清理资源的模式,有两个可选项

  • strict 严格模式
  • safe 安全模式,默认值

默认值的情况下,对于代码中动态引用资源,资源缩减会自行模糊判断一些资源,当作已使用资源。

例如,下面这个代码,就会把 img_ 为前缀的资源都算已引用资源。

String name = String.format("img_%1d", angle + 1);

res = getResources().getIdentifier(name, "drawable", getPackageName());
复制代码

如果设置了 strict 模式,就必须手动保留了,例如这样

<resources
    xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@drawable/img_*"
    tools:shrinkMode="strict">
</resources>
复制代码

配置生成的 APK 名称

//    配置 APK 名称
android.applicationVariants.all { variant ->
     variant.outputs.all {
        outputFileName = "Example_${variant.flavorName}_${variant.versionCode}_${variant.versionName}.apk"
     }
}
复制代码

动态配置 AndroidManifest 文件

动态配置 AndroidManifest 文件就是在构建过程中,动态修改 AndroidManifest 文件中的一些内容。

其本质是将构建变量注入到清单文件中。

在清单文件中是可以使用 占位符 的,格式是 ${variable} 。

例如常用的渠道编号

<!-- 渠道商编号 -->
<meta-data
    android:name="BaiduMobAd_CHANNEL"
    android:value="${CHANNEL_NAME}" />
复制代码

定义好占位符之后在 build.gradle 脚本里就可以通过 ProductFlavor 的 manifestPlaceholders 属性进行配置,它接受 Map 类型的参数。

//默认配置,它是一个 ProductFlavor
defaultConfig {
   ······
    manifestPlaceholders = [
            CHANNEL_NAME:"default"
    ]
}
复制代码

实际开发中通常是在产品变种配置渠道,方便日后运营统计查看

//变种维度
 flavorDimensions "channel"

 //产品变种
 productFlavors{

//        为了实践动态配置 AndroidManifest 文件
     huawei {
         dimension "channel"
         manifestPlaceholders = [
                 "CHANNEL_NAME":"华为"
         ]
     }
     mi {
         dimension "channel"
         manifestPlaceholders = [
                 CHANNEL_NAME:"小米"
         ]
     }
}
复制代码

默认情况下是有一个 ${applicationId} 占位符的,它会提供程序的应用ID。

例如

<intent-filter ... >
    <action android:name="${applicationId}.TRANSMOGRIFY" />
    ...
</intent-filter>
复制代码

自定义 BuildConfig

对于 BuildConfig 这个类,应该都很熟悉,这是一个由 构建工具生成的类。

BuildConfig

其中有一个 DEBUG 字段是我们常用的,用于标记是否在开发调试阶段,通常可以用作是否开启日志的开关,剩下的几个字段也都很熟悉,都是 build.gradle 中的配置。

除了构建工具自动生成的字段,也可以通过 ProductFlavor.buildConfigField(String type, String name, String value) 方法自定义 BuildConfig 的字段。

  • 第一个参数是 要生成的字段的类型
  • 第二个参数是 要生成的字段的名字
  • 第三个参数是 要生成的字段的常量值,如果是 String 类型类型的要带上引号。

例如,在 脚本里定义一个字段后

defaultConfig {

    buildConfigField "String" "WEBURL" "\"https://github.com/skymxc\""

}
复制代码

Sync now 之后就会在 BuildConfig 类中看到对应的字段了

自定义BuildConfig

这个配置在构建类型和产品变种里都是可以配置的,利用这个特性,就可以针对不同的构建类型,不同的产品变种配置不同的字段。

例如,分别配置开发和生产环境

defaultConfig {
    buildConfigField "String" "WEBURL" "\"https://github.com/skymxc\""
}

//构建类型
buildTypes {
    //发布类型
    release {
      ·······
        buildConfigField "String","WEBURL","\"https://www.cnblogs.com/skymxc/\""
    }
}
复制代码

也可以针对不同变种配置

//产品变种
productFlavors{

    //农村 变种
    countryside {
        dimension "domicile"
        buildConfigField "int","type","2"
    }
    
    //城市 变种
    city {
        dimension "domicile"
        buildConfigField "int","type","1"
    }

}
复制代码

Java 编译选项

Android Gradle 提供了一个 compileOptions{} 对 Java 编译进行配置

//Java 编译选项配置
compileOptions {
    // Java 源代码的编码
    encoding = 'utf-8'
    //Java 源代码编译级别
    sourceCompatibility JavaVersion.VERSION_1_8
    //生成的 Java 字节码版本
    targetCompatibility JavaVersion.VERSION_1_8
}
复制代码

常用的配置有三个

  • encoding Java源代码的编码
  • sourceCompatibility Java 源代码编译级别
  • targetCompatibility 生成的 Java 字节码版本

关于 64 K 限制

在打包 APK 文件时,Java 源代码被打包成了一个 DEX 文件,这个文件就是优化过的、Dalvik 虚拟机可执行的文件。

Dalvik 虚拟机在执行 DEX 文件的时候,它使用了 short 这个类型来索引 DEX 文件中的方法,这就意味着单个 DEX 文件最多只能是 65535 个,当方法数超过这个数量就会出现编译错误。

因为 1024 * 64 = 65535 也称 64K 限制。

解决办法就是生成多个 DEX 文件,让单个 DEX 文件内的方法数不超过 64K .

在 Android 5.0 及之后的版本使用 ART 了运行时,本身支持从 APK 中加载多个 DEX 文件。

ART 在应用安装时执行预编译,扫描 class[N].dex 文件,并将他们合成一个 .oat 文件,以供 Android 设备执行。

因此,如果你的 minSdkVersion 是 21 或者更高的版本,默认就会启用 MultiDex ,在打包时构建工具会自动生成多个 DEX 文件。

如果 miniSdkVersion 在 20或者更低,就需要在 build.gradle 脚本添加配置 multiDexEnabled true 启用 MultiDex,并添加 MultiDex 支持库。

android {

    defaultConfig {
        ...
        minSdkVersion 15
        targetSdkVersion 28
        multiDexEnabled true
    }
    ...

}
dependencies {
  implementation 'com.android.support:multidex:1.0.3’
}
复制代码

如果使用了 androidx ,则使用以下依赖

def multidex_version = "2.0.1"
implementation 'androidx.multidex:multidex:$multidex_version'
复制代码

添加完依赖库之后,还需要替换 Application 类

<?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) 以启用 MultiDex

public class MyApplication extends SomeOtherApplication {

  @Override
  protected void attachBaseContext(Context base) {
     super.attachBaseContext(base);
     MultiDex.install(this);
  }
}
复制代码

注意:在 MultiDex.install() 完成之前,不要通过反射或 JNI 执行 MultiDex.install() 或其他任何代码。MultiDex 跟踪功能不会追踪这些调用,从而导致出现 ClassNotFoundException,或因 DEX 文件之间的类分区错误而导致验证错误。

关于更多 64K 限制的文档可以看这里:启用 MultiDex

学习资料

End

最后把 Android Gradle 的 DSL 查询地址留一下Android Plugin DSL Reference 方便大家随用随查。

我把这些配置和注释都放在了 GitHub 上的一个仓库里,需要时查阅即可 AndroidGradleExample

微信扫一扫,关注我的公众号

佛系编码