添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

典型 Android 应用模块的构建流程通常依循下列步骤:

  • 编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中包括 Android 设备上运行的字节码),将所有其他内容转换成已编译资源。
  • APK 打包器将 DEX 文件和已编译资源合并成单个 APK。 不过,必须先签署 APK,才能将应用安装并部署到 Android 设备上。
  • APK 打包器使用调试或发布密钥库签署您的 APK:
  • 在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时占用的内存。
  • 简要步骤:

  • Merged Manifest, Merged Resource, Merged Assets --> aapt --> R.java, Compiled Resource
  • R.java, Source Code --> Java Compiler --> .class files --> proguard --> proguarded.jar file --> dex --> .dex files
  • Compiled Resource, .dex files, .so files... --> apkbuilder --> .apk file --> sign --> sign.apk file --> zipalign
  • 构建配置文件

    Android Plugin for Gradle 引入了许多 DSL 元素,具体可参考: DSL 参考文档

    settings.gradle 文件位于项目根目录,用于指示 Gradle 在构建应用时应将哪些模块包括在内。对大多数项目而言,该文件很简单,只包括以下内容:

    1
    include ':app'

    顶级 build.gradle 文件位于项目根目录,用于定义适用于项目中所有模块的构建配置。 默认情况下,此顶级构建文件使用 buildscript 代码块来定义项目中所有模块共用的 Gradle 存储区和依赖项。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    /**
    * The buildscript block is where you configure the repositories and
    * dependencies for Gradle itself—meaning, you should not include dependencies
    * for your modules here. For example, this block includes the Android plugin for
    * Gradle as a dependency because it provides the additional instructions Gradle
    * needs to build Android app modules.
    */

    buildscript {

    /**
    * The repositories block configures the repositories Gradle uses to
    * search or download the dependencies. Gradle pre-configures support for remote
    * repositories such as JCenter, Maven Central, and Ivy. You can also use local
    * repositories or define your own remote repositories. The code below defines
    * JCenter as the repository Gradle should use to look for its dependencies.
    *
    * New projects created using Android Studio 3.0 and higher also include
    * Google's Maven repository.
    */

    repositories {
    google()
    jcenter()
    }

    /**
    * The dependencies block configures the dependencies Gradle needs to use
    * to build your project. The following line adds Android plugin for Gradle
    * version 3.4.2 as a classpath dependency.
    */

    dependencies {
    classpath 'com.android.tools.build:gradle:3.4.2'
    }
    }

    /**
    * The allprojects block is where you configure the repositories and
    * dependencies used by all modules in your project, such as third-party plugins
    * or libraries. However, you should configure module-specific dependencies in
    * each module-level build.gradle file. For new projects, Android Studio
    * includes JCenter and Google's Maven repository by default, but it does not
    * configure any dependencies (unless you select a template that requires some).
    */

    allprojects {
    repositories {
    google()
    jcenter()
    }
    }

    对于包含多个模块的 Android 项目,在项目级别定义某些属性,并在所有模块间共享这些属性可能会非常有用。为此,可以将额外属性添加到顶级 build.gradle 文件的 ext 代码块中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    buildscript {...}

    allprojects {...}

    // This block encapsulates custom properties and makes them available to all
    // modules in the project.
    ext {
    // The following are only a few examples of the types of properties you can define.
    compileSdkVersion = 28
    // You can also create properties to specify versions for dependencies.
    // Having consistent versions between modules can avoid conflicts with behavior.
    supportLibVersion = "28.0.0"
    ...
    }

    要从相同项目中的模块访问这些属性,在模块的 build.gradle 文件中使用以下语法(虽然 Gradle 可在模块级别定义项目范围的属性,但应避免这样做,因为这样会导致共享这些属性的模块进行耦合)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    android {
    // Use the following syntax to access properties you defined at the project level:
    // rootProject.ext.property_name
    compileSdkVersion rootProject.ext.compileSdkVersion
    ...
    }
    ...
    dependencies {
    implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
    ...
    }

    模块级 build.gradle 文件位于各 project/module/ 目录中,用于配置适用于其所在模块的构建设置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    /**
    * The first line in the build configuration applies the Android plugin for
    * Gradle to this build and makes the android block available to specify
    * Android-specific build options.
    */

    apply plugin: 'com.android.application'

    /**
    * The android block is where you configure all your Android-specific
    * build options.
    */

    android {

    /**
    * compileSdkVersion specifies the Android API level Gradle should use to
    * compile your app. This means your app can use the API features included in
    * this API level and lower.
    */

    compileSdkVersion 28

    /**
    * buildToolsVersion specifies the version of the SDK build tools, command-line
    * utilities, and compiler that Gradle should use to build your app. You need to
    * download the build tools using the SDK Manager.
    *
    * This property is optional because the plugin uses a recommended version of
    * the build tools by default.
    */

    buildToolsVersion "29.0.0"

    /**
    * The defaultConfig block encapsulates default settings and entries for all
    * build variants, and can override some attributes in main/AndroidManifest.xml
    * dynamically from the build system. You can configure product flavors to override
    * these values for different versions of your app.
    */

    defaultConfig {

    /**
    * applicationId uniquely identifies the package for publishing.
    * However, your source code should still reference the package name
    * defined by the package attribute in the main/AndroidManifest.xml file.
    */

    applicationId 'com.example.myapp'

    // Defines the minimum API level required to run the app.
    minSdkVersion 15

    // Specifies the API level used to test the app.
    targetSdkVersion 28

    // Defines the version number of your app.
    versionCode 1

    // Defines a user-friendly version name for your app.
    versionName "1.0"
    }

    /**
    * The buildTypes block is where you can configure multiple build types.
    * By default, the build system defines two build types: debug and release. The
    * debug build type is not explicitly shown in the default build configuration,
    * but it includes debugging tools and is signed with the debug key. The release
    * build type applies Proguard settings and is not signed by default.
    */

    buildTypes {

    /**
    * By default, Android Studio configures the release build type to enable code
    * shrinking, using minifyEnabled, and specifies the Proguard settings file.
    */

    release {
    minifyEnabled true // Enables code shrinking for the release build type.
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    }

    /**
    * The productFlavors block is where you can configure multiple product flavors.
    * This allows you to create different versions of your app that can
    * override the defaultConfig block with their own settings. Product flavors
    * are optional, and the build system does not create them by default.
    *
    * This example creates a free and paid product flavor. Each product flavor
    * then specifies its own application ID, so that they can exist on the Google
    * Play Store, or an Android device, simultaneously.
    *
    * If you declare product flavors, you must also declare flavor dimensions
    * and assign each flavor to a flavor dimension.
    */

    flavorDimensions "tier"
    productFlavors {
    free {
    dimension "tier"
    applicationId 'com.example.myapp.free'
    }

    paid {
    dimension "tier"
    applicationId 'com.example.myapp.paid'
    }
    }

    /**
    * The splits block is where you can configure different APK builds that
    * each contain only code and resources for a supported screen density or
    * ABI. You'll also need to configure your build so that each APK has a
    * different versionCode.
    */

    splits {
    // Settings to build multiple APKs based on screen density.
    density {

    // Enable or disable building multiple APKs.
    enable false

    // Exclude these densities when building multiple APKs.
    exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
    }
    }
    }

    /**
    * The dependencies block in the module-level build configuration file
    * specifies dependencies required to build only the module itself.
    * To learn more, go to Add build dependencies.
    */

    dependencies {
    implementation project(":lib")
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    }

    Gradle 还包括两个属性文件,均位于项目根目录中,可用于指定适用于 Gradle 构建工具包本身的设置:

  • gradle.properties:可以在其中配置项目范围 Gradle 设置,例如 Gradle 后台进程的最大堆大小。 如需了解详细信息,请参阅构建环境。
  • local.properties:为构建系统配置本地环境属性,例如 SDK 安装路径。由于该文件的内容由 Android Studio 自动生成并且专用于本地开发者环境,因此不应手动修改该文件,或将其纳入版本控制系统。
  • Android Studio 按逻辑关系将每个模块的源代码和资源分组为源集。模块的 main/ 源集包括其所有构建变体使用的代码和资源。其他源集目录为可选项,在配置新的构建变体时,Android Studio 不会自动为您创建这些目录。不过,创建类似于 main/ 的源集有助于让 Gradle 仅在构建特定应用版本时才应使用的文件和资源井然有序:

  • src/main/:此源集包括所有构建变体共用的代码和资源。
  • src/buildType/:创建此源集可加入特定构建类型专用的代码和资源。
  • src/productFlavor/:创建此源集可加入特定产品风格专用的代码和资源。(注:如果配置构建以组合多个产品风格,则可为风格维度间产品风格的各个组合创建源集目录: src/productFlavor1ProductFlavor2/)
  • src/productFlavorBuildType/:创建此源集可加入特定构建变体专用的代码和资源。
  • 例如,要生成应用的“完整调试”版本,构建系统需要合并来自以下源集的代码、设置和资源:

  • src/fullDebug/(构建变体源集)
  • src/debug/(构建类型源集)
  • src/full/(产品风格源集)
  • src/main/(主源集)
  • 注:当在 Android Studio 中使用 File > New 菜单选项新建文件或目录时,可以针对特定源集进行创建。 可供您选择的源集取决于您的构建配置,如果所需目录尚不存在,Android Studio 会自动创建。

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

  • 构建变体 > 构建类型 > 产品风格 > 主源集 > 库依赖项
  • 这样一来,Gradle 便可使用专用于您试图构建的构建变体的文件,同时对与其他应用版本共用的 Activity、应用逻辑和资源加以重复利用。 在合并多个清单时,Gradle 使用同一优先顺序,这样每个构建变体都能在最终清单中定义不同的组件或权限。

    android对象为我们提供了3个属性:

  • applicationVariants (仅仅适用于Android应用Gradle插件)
  • libraryVariants (仅仅适用于Android库Gradle插件)
  • testVariants (以上两种Gradle插件都使用)
  • SDK Version

    compileSdkVersion

    compileSdkVersion仅仅是告诉Gradle使用哪个版本的SDK编译应用,不会被包含到apk中,完全不影响应用的运行结果,关注compileSdkVersion版本的原因:

  • 应用想兼容新版本、使用了新版本API,此时就必须使用新版本及以上版本编译,否则就会编译报错;
  • 如果使用了新版本的Support Library,此时也必须使用新版本及以上版本编译;
  • 推荐使用最新版本编译,用新的编译检查,可以看到很多新版本相关的警告,提前预研新版本开发;
  • minSdkVersion

    1. minSdkVersion表明此应用兼容的最低版本,在低于该版本的手机上安装时会报错,无法安装;
    2. 如果最低版本设置为19,在代码中使用了API 23中的API,就会有警告。使用运行时检查系统版本的方式可解决;
    3. 如果使用的某个Support Library的最低版本为7,那minSdkVersion就必须大于等于7了,否则该Support Library在低于7的手机中就要报错了。
    4. targetSdkVersion

      1. 如果targetSdkVersion为19(对应为Android4.4),应用运行时,最高只能使用API 19的新特性。即使代码中使用了API 23的新特性,实际运行时,也不会使用该新特性;
      2. 同样的API,比如AlarmManger的set()和get()方法,在API 19和之前的效果是不一样的,如果targetSdkVersion为18,无论运行手机是什么版本,都是旧效果;如果targetSdkVersion为19,那么在4.4以上的手机上运行时,就是新效果了。
      3. 总结

        综上所诉,compileSdkVersion决定了编译期间能否使用新版本的API。targetSDKVersion决定了运行期间使用哪种特性。建议用较低的minSdkVersion来覆盖最大的人群,用最新的compileSdkVersion和targetSDKVersion来获得最好的外观和行为。即:maxSdkVersion >= buildToolsVersion >= compileSdkVersion>= targetSdkVersion >= minSdkVersion

        Set the Application ID

        设置Application ID

        应用 ID 通过模块的 build.gradle 文件中的 applicationId 属性定义,如下所示:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        android {
        defaultConfig {
        applicationId "com.example.myapp"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        }
        ...
        }

        应用 ID 和软件包名称彼此无关,可以更改代码的软件包名称(代码命名空间),这不会影响应用 ID,反之亦然。Application ID的命名规则的限制:

      4. 必须至少包含两段(一个或多个圆点)。
      5. 每段必须以字母开头。
      6. 所有字符必须为字母数字或下划线 [a-zA-Z0-9_]。
      7. 应用 ID 过去直接关联到代码的软件包名称;所以,有些 Android API 会在其方法名称和参数名称中使用“package name”一词,但这实际上是Application ID。例如,Context.getPackageName() 方法会返回您的应用 ID。
      8. 使用 WebView的话,Application ID 中应将软件包名称用作前缀;否则,可能会遇到如 问题 211768 中所述的问题。
      9. 更改用于编译变体的Application ID

        每个编译变体应定义为单独的产品特性。对于 productFlavors 块中的每个类型,可以重新定义 applicationId 属性,也可以使用 applicationIdSuffix 在默认的应用 ID 上追加一段,如下所示:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        android {
        defaultConfig {
        applicationId "com.example.myapp"
        }
        productFlavors {
        free {
        applicationIdSuffix ".free"
        }
        pro {
        applicationIdSuffix ".pro"
        }
        }
        }

        也可以根据自己的版本类型使用 applicationIdSuffix 追加一段,如下所示:

        1
        2
        3
        4
        5
        6
        7
        8
        android {
        ...
        buildTypes {
        debug {
        applicationIdSuffix ".debug"
        }
        }
        }

        由于 Gradle 会在产品特性后面应用版本类型配置,因此“free debug”编译变体的应用 ID 现在是“com.example.myapp.free.debug”。

      10. 为了与以前的 SDK 工具兼容,如果未在 build.gradle 文件中定义 applicationId 属性,构建工具会将 AndroidManifest.xml 文件中的软件包名称用作应用 ID。在这种情况下,重构软件包名称也会更改您的应用 ID。
      11. 如果需要在清单文件中引用应用 ID,可以在任何清单属性中使用 ${applicationId} 占位符。在编译期间,Gradle 会将此标记替换为实际的应用 ID。
      12. 更改用于测试的应用 ID

        默认情况下,构建工具会将应用 ID 应用到您的测试 APK,该 APK 将应用 ID 用于给定的编译变体,同时追加 .test。例如,com.example.myapp.free 编译变体的测试 APK 的应用 ID 为 com.example.myapp.free.test。

        可以通过在 defaultConfig 或 productFlavor 块中定义 testApplicationId 属性来更改应用 ID,不过应该没有必要这样做。

        注意:为了避免与受测应用发生名称冲突,构建工具会为您的测试 APK 生成 R 类,其命名空间基于测试应用 ID,而不是清单文件中定义的软件包名称。

        更改软件包名称

        默认情况下,项目的软件包名称与应用 ID 匹配,但您可以更改软件包名称。不过,如果您要更改软件包名称,需要注意的是,软件包名称(由项目目录结构定义)应始终与 AndroidManifest.xml 文件中的 package 属性匹配,如下所示:

        1
        2
        3
        4
        5
        <?xml version="1.0" encoding="utf-8"?>
        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapp"
        android:versionCode="1"
        android:versionName="1.0" >

        Android 构建工具使用 package 属性来发挥两种作用:

      13. 它会将此名称用作应用生成的 R.java 类的命名空间。示例:对于上面的清单,R 类将为 com.example.myapp.R。
      14. 它会使用此名称解析清单文件中声明的任何相关类名。示例:对于上面的清单,声明为 <activity android:name=".MainActivity"> 的 Activity 将解析为 com.example.myapp.MainActivity。
      15. 因此,package 属性中的名称应始终与项目的基础软件包名称匹配,基础软件包中保存着您的 Activity 及其他应用代码。当然,您的项目中可以包含子软件包,但是这些文件必须从 package 属性导入使用命名空间的 R.java 类,而且清单中声明的任何应用组件都必须添加缺失的子软件包名称(或者使用完全限定软件包名称)。

        如果您要完全重构您的软件包名称,请确保也更新 package 属性。只要您使用 Android Studio 的工具重命名和重构您的软件包,那么这些就会自动保持同步。(如果它们未保持同步,您的应用代码将无法解析 R 类,因为它不再位于同一软件包中,并且清单无法识别您的 Activity 或其他组件。)

        您必须始终在项目的主 AndroidManifest.xml 文件中指定 package 属性。如果您有其他清单文件(如产品特性或版本类型的清单文件),请注意,优先级最高的清单文件提供的软件包名称始终用于最终合并的清单。

        还有一点需要了解:虽然清单 package 和 Gradle applicationId 可以具有不同的名称,但构建工具会在编译结束时将应用 ID 复制到 APK 的最终清单文件中。所以,如果您在编译后检查 AndroidManifest.xml 文件,发现 package 属性发生更改就不足为奇了。实际上,Google Play 商店和 Android 平台会查看 package 属性来识别您的应用。所以,编译系统利用原始值(设置 R 类的命名空间并解析清单类名称)后,它会舍弃该值并将其替换为应用 ID。

        Add the dependencies

        指定依赖项时,不应使用动态版本号,比如 'com.android.tools.build:gradle:3.+' 。 使用此功能,可能会导致意外版本更新和难以解析版本差异。

        依赖项类型

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        apply plugin: 'com.android.application'

        android { ... }

        dependencies {
        // Dependency on a local library module
        implementation project(":mylibrary")

        // Dependency on local binaries
        implementation fileTree(dir: 'libs', include: ['*.jar'])

        // Dependency on a remote binary
        implementation 'com.example.android:app-magic:12.3'
        }

        本地库模块依赖项

        1
        implementation project(':mylibrary')

        这段代码声明名为“mylibrary”的 Android 库模块的依赖项(该名称必须匹配使用 settings.gradle 文件中的 include: 定义的库名称)。在构建应用时,构建系统会编译库模块,并将生成的编译内容打包到 APK中。

        本地二进制文件依赖项

        1
        implementation fileTree(dir: 'libs', include: ['*.jar'])

        Gradle 声明项目 module_name/libs/ 目录中 JAR 文件的依赖项(因为 Gradle 会读取 build.gradle 文件的相对路径)。或者,也可以像下面这样指定单独的文件:

        1
        implementation files('libs/foo.jar', 'libs/bar.jar')

        远程二进制文件依赖项

        1
        implementation 'com.example.android:app-magic:12.3'

        以上代码实际上是下列代码的缩写形式:

        1
        implementation group: 'com.example.android', name: 'app-magic', version: '12.3'

        这段代码声明 com.example.android 命名空间组内“app-magic”库 12.3 版本的依赖项。

        注:与此类似的远程依赖项要求您声明相应的远程代码库,Gradle 应在其中寻找该库。如果本地尚不存在该库,Gradle 会在构建需要它时(例如,当您点击 Sync Project with Gradle Files 或当您运行构建时)从远程站点获取该库。

        依赖项配置

        以上配置适用于您的项目的主源集,该源集应用于所有构建不同类型。 如果您改为只想为特定构建不同类型源集或测试源集声明依赖项,则必须大写配置名称并在其前面加上构建不同类型或测试源集的名称作为前缀。

        例如,要仅将 implementation 依赖项添加到您的“free”产品风格(使用远程二进制文件依赖项),需要使用下面这样的代码:

        已弃用配置 implementation compile Gradle 会将依赖项添加到编译类路径,并将依赖项打包到构建输出。但是,当您的模块配置 implementation 依赖项时,会告知 Gradle 您不想模块在编译时将依赖项泄露给其他模块。也就是说,依赖项只能在运行时供其他模块使用。使用此依赖项配置而不是api 或 compile(已弃用),可以显著缩短构建时间,因为它可以减少构建系统需要重新编译的模块数量。例如,如果 implementation 依赖项更改了其 API,Gradle 只会重新编译该依赖项和直接依赖它的模块。大多数应用和测试模块都应使用此配置。 compile Gradle 会将依赖项添加到编译类路径,并构建输出。当模块包括 api 依赖项时,会告知 Gradle 模块想将该依赖项间接导出至其他模块,以使这些模块在运行时和编译时均可使用该依赖项。此配置的行为类似于 compile (现已弃用),但您应仅对需要间接导出至其他上游消费者的依赖项慎重使用它。 这是因为,如果 api 依赖项更改了其外部 API,Gradle 会重新编译可以在编译时访问该依赖项的所有模块。 因此,拥有大量 api 依赖项会显著增加构建时间。 如果不想向不同的模块公开依赖项的 API,库模块应改用 implementation 依赖项。 compileOnly provided Gradle 只会将依赖项添加到编译类路径(即不会将其添加到构建输出)。如果是创建 Android 模块且在编译期间需要使用该依赖项,在运行时可选择呈现该依赖项,则此配置会很有用。如果使用此配置,则您的库模块必须包含运行时条件,以便检查是否提供该依赖项,然后妥善更改其行为,以便模块在未提供依赖项的情况下仍可正常工作。这样做不会添加不重要的瞬时依赖项,有助于缩减最终 APK 的大小。 此配置的行为类似于 provided (现已弃用)。 runtimeOnly Gradle 只会将依赖项添加到构建输出,供运行时使用。也就是说,不会将其添加到编译类路径。 此配置的行为类似于 apk(现已弃用)。 annotationProcessor compile 要在库中添加注解处理器依赖项,则必须使用 annotationProcessor 配置将其添加到注解处理器类路径。这是因为使用此配置可分离编译类路径与注解处理器类路径,从而提升构建性能。如果 Gradle 在编译类路径上找到注解处理器,则会停用 避免编译功能,这样会增加构建时间(Gradle 5.0 和更高版本会忽略编译类路径上的注解处理器)。如果 JAR 文件包含以下文件,则 Android Gradle Plugin 会假定依赖项是注解处理器:META-INF/services/javax.annotation.processing.Processor。如果插件检测到编译类路径上包含注解处理器,则会生成构建错误。
        1
        2
        3
        dependencies {
        freeImplementation 'com.google.firebase:firebase-ads:9.8.0'
        }

        但如果想要为组合产品风格和构建类型的变体添加依赖项,则必须在 configurations 代码块中初始化配置名称。 以下示例向您的“freeDebug”构建变体添加 runtimeOnly 依赖项(使用本地二进制文件依赖项):

        1
        2
        3
        4
        5
        6
        7
        8
        9
        configurations {
        // Initializes a placeholder for the freeDebugRuntimeOnly dependency
        // configuration.
        freeDebugRuntimeOnly {}
        }

        dependencies {
        freeDebugRuntimeOnly fileTree(dir: 'libs', include: ['*.jar'])
        }

        要为您的本地测试和设备化测试添加 implementation 依赖项,需要使用下面这样的代码:

        1
        2
        3
        4
        5
        6
        7
        dependencies {
        // Adds a remote binary dependency only for local tests.
        testImplementation 'junit:junit:4.12'

        // Adds a remote binary dependency only for the instrumented test APK.
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        }

        但某些配置在这种情况下没有意义。 例如,由于其他模块无法依赖 androidTest,因此如果使用 androidTestApi 配置,则会收到以下警告:

        1
        WARNING: Configuration 'androidTestApi' is obsolete and has been replaced with 'androidTestImplementation'.

        添加注解处理器

        如果将注解处理器添加到您的编译类路径,您将看到一条与以下消息类似的错误消息:

        1
        Error: Annotation processors must be explicitly declared now.

        要解决此错误问题,请使用 annotationProcessor 配置您的依赖项,以在您的项目中添加注解处理器,如下所示:

        1
        2
        3
        4
        5
        6
        dependencies {
        // Adds libraries defining annotations to only the compile classpath.
        compileOnly 'com.google.dagger:dagger:version-number'
        // Adds the annotation processor dependency to the annotation processor classpath.
        annotationProcessor 'com.google.dagger:dagger-compiler:version-number'
        }

        如果需要向注解处理器传递参数,您可以在您的模块构建配置中使用 AnnotationProcessorOptions 代码块。 例如,如果要以键值对形式传递原始数据类型,则可使用 argument 属性,如下所示:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        android {
        ...
        defaultConfig {
        ...
        javaCompileOptions {
        annotationProcessorOptions {
        argument "key1", "value1"
        argument "key2", "value2"
        }
        }
        }
        }

        但在使用 Android Gradle Plugin 3.2.0 和更高版本时,您需要使用 Gradle CommandLineArgumentProvider 接口传递表示文件或目录的处理器参数。使用 CommandLineArgumentProvider 可让您或注解处理器作者将增量构建属性类型注解应用于每个参数,从而提高增量构建和缓存干净构建的正确性和性能。

        例如,下面的类可实现 CommandLineArgumentProvider 并注解处理器的每个参数。 此外,此示例也使用 Groovy 语言语法,且直接包含在模块的 build.gradle 文件中。

        注:通常,注解处理器作者会提供此类或有关如何编写这种类的说明。这是因为每个参数均需指定正确的构建属性类型注解,才能按预期运行。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        class MyArgsProvider implements CommandLineArgumentProvider {

        // Annotates each directory as either an input or output for the
        // annotation processor.
        @InputFiles
        // Using this annotation helps Gradle determine which part of the file path
        // should be considered during up-to-date checks.
        @PathSensitive(PathSensitivity.RELATIVE)
        FileCollection inputDir

        @OutputDirectory
        File outputDir

        // The class constructor sets the paths for the input and output directories.
        MyArgsProvider(FileCollection input, File output) {
        inputDir = input
        outputDir = output
        }

        // Specifies each directory as a command line argument for the processor.
        // The Android plugin uses this method to pass the arguments to the
        // annotation processor.
        @Override
        Iterable<String> asArguments() {
        // Use the form '-Akey[=value]' to pass your options to the Java compiler.
        ["-AinputDir=${inputDir.singleFile.absolutePath}",
        "-AoutputDir=${outputDir.absolutePath}"]
        }
        }

        android {...}

        在创建实现 CommandLineArgumentProvider 的类后,您需要使用 annotationProcessorOptions.compilerArgumentProvider 属性初始化并将其传递至 Android 插件,如下所示。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        // This is in your module's build.gradle file.
        android {
        defaultConfig {
        javaCompileOptions {
        annotationProcessorOptions {
        // Creates a new MyArgsProvider object, specifies the input and
        // output paths for the constructor, and passes the object
        // to the Android plugin.
        compilerArgumentProvider new MyArgsProvider(files("input/path"),
        new File("output/path"))
        }
        }
        }
        }

        如果编译类路径中的依赖项包含您不需要的注解处理器,您可以将以下代码添加到 build.gradle 文件中,停用错误检查。 请记住,您添加到编译类路径中的注解处理器仍不会添加到处理器类路径中。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        android {
        ...
        defaultConfig {
        ...
        javaCompileOptions {
        annotationProcessorOptions {
        includeCompileClasspath false
        }
        }
        }
        }

        如果您将项目的注解处理器迁移到处理器类路径后遇到问题,可通过将 includeCompileClasspath 设置为 true,允许编译类路径上包含注解处理器。 但是,我们不建议将此属性设置为 true,并且我们将在以后的 Android plugin 更新版本中移除这种操作的相关选项。

        排除传递依赖项

        随着应用范围的扩大,其中可包含许多依赖项,包括直接依赖项和传递依赖项(应用的导入库所依赖的库)。 要排除不再需要的传递依赖项,您可以使用 exclude 关键字,如下所示:

        1
        2
        3
        4
        5
        dependencies {
        implementation('some-library') {
        exclude group: 'com.example.imgtools', module: 'native'
        }
        }

        如果需要从您的测试中排除某些传递依赖项,上文所示的代码示例可能无法按预期发挥作用。 这是因为测试配置(例如 androidTestImplementation)扩展了模块的 implementation 配置。 也就是说,在 Gradle 解析配置时其中始终包含 implementation 依赖项。

        因此,要从测试中排除传递依赖项,必须在执行代码时执行此操作,如下所示:

        1
        2
        3
        4
        android.testVariants.all { variant ->
        variant.getCompileConfiguration().exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
        variant.getRuntimeConfiguration().exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
        }

        使用 variant-aware 依赖项管理

        Android 插件 3.0.0 及更高版本包含一项新的依赖项机制,这种机制可以在消费库时自动匹配不同类型。 也就是说,应用的 debug 不同类型将自动消费库的 debug 不同类型,依此类推。 这种机制也适用于使用风格的情况—应用的 freeDebug 变体将使用库的 freeDebug 变体。

        要让插件准确匹配变体,您需要为无法直接匹配的情况提供匹配回退。 假设您的应用配置一个名为“staging”的构建类型,但其库依赖项之一没有进行相应配置。 在插件尝试构建您的“staging”版本的应用时,它将无法了解库要使用哪一个版本,您将看到一条类似以下消息的错误消息:

        1
        2
        3
        Error:Failed to resolve: Could not resolve project :mylibrary.
        Required by:
        project :app

        远程代码库

        如果您的依赖项并非本地库或文件树,Gradle 会在您的 build.gradle 文件 repositories 程序块中指定的任何一个在线代码库中寻找文件。 列出各代码库的顺序决定了 Gradle 在这些代码库中搜索各项目依赖项的顺序。 例如,如果代码库 A 和 B 都提供某依赖项,而您先列出代码库 A,则 Gradle 会从代码库 A 下载此依赖项。

        默认情况下,Android Studio 新项目会在项目的顶级 build.gradle 文件中指定 Google 的 Maven 代码库和 JCenter 作为代码库位置,如下所示:

        1
        2
        3
        4
        5
        6
        allprojects {
        repositories {
        google()
        jcenter()
        }
        }

        如果您需要的内容来自 Maven 中央代码库,则添加 mavenCentral();如果来自本地代码库,则使用 mavenLocal(),或者也可像下面这样声明特定 Maven 或 Ivy 代码库:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        allprojects {
        repositories {
        google()
        jcenter()
        mavenCentral()
        mavenLocal()
        maven {
        url "https://repo.example.com/maven2"
        }
        maven {
        url "file://local/repo/"
        }
        ivy {
        url "https://repo.example.com/ivy"
        }
        }
        }

        依赖项顺序

        例如,如果您的项目声明以下内容:

      16. LIB_A 和 LIB_B 上的依赖项(按照该顺序)
      17. 并且 LIB_A 依赖 LIB_C 和 LIB_D (按照该顺序)
      18. 并且 LIB_B 还依赖 LIB_C
      19. 然后,扁平型依赖项顺序将如下所示:

      20. LIB_A
      21. LIB_D
      22. LIB_B
      23. LIB_C
      24. 查看依赖项树

        运行 gradle androidDependencies 即可查看依赖项树。

        修复依赖项解析错误

        修复重复类错误

        如果某类多次出现在运行时类路径中,您会收到一条与以下内容相似的错误:

        1
        Program type already present com.example.MyClass

        该错误通常是下列其中一种情况所致:

      25. 二进制文件依赖项包括您的应用同时作为直接依赖项包括的库。 例如,您的应用在库 A 和库 B 上声明了直接依赖项,但库 A 的二进制文件中已包括库 B:要解决此问题,请取消将库 B 作为直接依赖项。
      26. 您的应用在同一库上具有本地二进制文件依赖项和远程二进制文件依赖项:要解决此问题,请移除其中一个二进制文件依赖项。
      27. 解决类路径之间的冲突问题

        当 Gradle 解析编译类路径时,会先解析运行时类路径,然后使用此结果确定应添加到编译类路径的依赖项版本。 换言之,运行时类路径决定下游类路径的相同依赖项所需的版本号。

        应用的运行时类路径还决定 Gradle 匹配运行类路径中应用测试 APK 的依赖项所需要的版本号:

        如果相同依赖项的冲突版本出现在多个类路径中,您可能会看到与以下内容相似的错误:

        1
        2
        Conflict with dependency 'com.example.library:some-lib:2.0' in project 'my-library'.
        Resolved versions for runtime classpath (1.0) and compile classpath (2.0) differ.

        例如,当您的应用使用 implementation 依赖项配置加入某依赖项版本,并且库模块使用 runtimeOnly 配置加入此依赖项的不同版本时,可能会发生该冲突。 要解决此问题,请执行以下其中一项操作:

      28. 将所需版本的依赖项作为 api 依赖项加入您的库模块。 也就是说,仅库模块声明此依赖项,但应用模块也可间接访问其 API。
      29. 或者,您也可以同时在两个模块中声明此依赖项,但应确保每个模块使用的版本相同。 请考虑配置项目范围的属性,以确保各依赖项的多个版本在整个项目中都保持一致。
      30. 应用自定义构建逻辑

        本节介绍的内容在您想要扩展 Android Gradle Plugin 或编写自己的插件时很有用。

        为自定义逻辑发布变体依赖项

        库可以包含其他项目或子项目可能要使用的功能。 发布库是为其消费者提供库的流程。 库可以控制其消费者在编译时和运行时可访问的依赖项。现有两种不同的配置,其中包含消费者为使用库而必须使用的各类路径的传递依赖项,如下所述:

        1
        2
        variant_nameApiElements:此配置包含编译时消费者可使用的传递依赖项。
        variant_nameRuntimeElements:此配置包含运行时消费者可使用的传递依赖项。

        自定义依赖项解析策略

        项目包含的依赖项可能包含在相同库的两个不同版本中,这样会导致依赖项冲突。例如,如果您的项目依赖于模块 A 的版本 1 和模块 B 的版本 2,模块 A 间接依赖于模块 B 的版本 3,则会出现依赖项版本冲突。

        要解决此冲突问题,Android Gradle Plugin 需使用以下依赖项解析策略:当插件检测到依赖图中包含相同模块的不同版本时,会默认选择版本最高的模块。但此策略可能无法按预期发挥作用。 要自定义依赖项解析策略,请使用以下配置解析您任务所需变体的特定依赖项:

        1
        2
        variant_nameCompileClasspath:此配置包含适用于给定变体编译类路径的解析策略。
        variant_nameRuntimeClasspath:此配置包含适用于给定变体运行时类路径的解析策略。

        Android Gradle Plugin 包含可用于访问各变体配置对象的 getter。 因此,您可以使用变体 API 查询依赖项解析策略,如下例所示:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        android {
        applicationVariants.all { variant ->
        // Return compile configuration objects of a variant.
        variant.getCompileConfiguration().resolutionStrategy {
        // Use Gradle's ResolutionStrategy API
        // to customize how this variant resolves dependencies.
        ...
        }
        // Return runtime configuration objects of a variant.
        variant.getRuntimeConfiguration().resolutionStrategy {
        ...
        }
        // Return annotation processor configuration of a variant.
        variant.getAnnotationProcessorConfiguration().resolutionStrategy {
        ...
        }
        }
        }

        配置编译变体

        配置版本类型

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        android {
        defaultConfig {
        manifestPlaceholders = [hostName:"www.example.com"]
        ...
        }
        buildTypes {
        release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
        applicationIdSuffix ".debug"
        debuggable true
        }

        /**
        * The `initWith` property allows you to copy configurations from other build types,
        * then configure only the settings you want to change. This one copies the debug build
        * type, and then changes the manifest placeholder and application ID.
        */
        staging {
        initWith debug
        manifestPlaceholders = [hostName:"internal.example.com"]
        applicationIdSuffix ".debugStaging"
        }
        }
        }

        配置产品特性

        配置产品特性

        创建产品特性与创建版本类型类似:将其添加到编译配置中的 productFlavors 代码块并添加所需的设置。产品特性支持与 defaultConfig 相同的属性,这是因为 defaultConfig 实际上属于 ProductFlavor 类。这意味着,您可以在 defaultConfig 代码块中为所有类型提供基本配置,并且每个类型都可以更改其中任何默认值.

        所有类型都必须属于一个指定的类型维度,即一个产品特性组。即使您打算只使用一个维度,也必须将类型分配到类型维度.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        android {
        ...
        defaultConfig {...}
        buildTypes {
        debug{...}
        release{...}
        }
        // Specifies one flavor dimension.
        flavorDimensions "version"
        productFlavors {
        demo {
        // Assigns this product flavor to the "version" flavor dimension.
        // This property is optional if you are using only one dimension.
        dimension "version"
        applicationIdSuffix ".demo"
        versionNameSuffix "-demo"
        }
        full {
        dimension "version"
        applicationIdSuffix ".full"
        versionNameSuffix "-full"
        }
        }
        }

        将多个产品特性与类型维度结合使用

        在编译应用时,Gradle 会结合使用您定义的每个类型维度的产品特性配置以及版本类型配置,以创建最终的编译变体。Gradle 不会将属于同一类型维度的产品特性组合在一起。

        以下代码示例使用 flavorDimensions 属性来创建“mode”类型维度和“api”类型维度,前者用于将“full”和“demo”产品特性进行分组,后者用于根据 API 级别对产品特性配置进行分组:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        android {
        ...
        buildTypes {
        debug {...}
        release {...}
        }

        // Specifies the flavor dimensions you want to use. The order in which you
        // list each dimension determines its priority, from highest to lowest,
        // when Gradle merges variant sources and configurations. You must assign
        // each product flavor you configure to one of the flavor dimensions.
        flavorDimensions "api", "mode"

        productFlavors {
        demo {
        // Assigns this product flavor to the "mode" flavor dimension.
        dimension "mode"
        ...
        }

        full {
        dimension "mode"
        ...
        }

        // Configurations in the "api" product flavors override those in "mode"
        // flavors and the defaultConfig block. Gradle determines the priority
        // between flavor dimensions based on the order in which they appear next
        // to the flavorDimensions property above--the first dimension has a higher
        // priority than the second, and so on.
        minApi24 {
        dimension "api"
        minSdkVersion 24
        // To ensure the target device receives the version of the app with
        // the highest compatible API level, assign version codes in increasing
        // value with API level. To learn more about assigning version codes to
        // support app updates and uploading to Google Play, read Multiple APK Support
        versionCode 30000 + android.defaultConfig.versionCode
        versionNameSuffix "-minApi24"
        ...
        }

        minApi23 {
        dimension "api"
        minSdkVersion 23
        versionCode 20000 + android.defaultConfig.versionCode
        versionNameSuffix "-minApi23"
        ...
        }

        minApi21 {
        dimension "api"
        minSdkVersion 21
        versionCode 10000 + android.defaultConfig.versionCode
        versionNameSuffix "-minApi21"
        ...
        }
        }
        }

        以上面的编译配置为例,Gradle 使用以下命名方案创建了总共 12 个编译变体:

      31. 编译变体:[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
      32. 对应的 APK:app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
      33. 除了可以为各个产品特性和编译变体创建源集目录外,您还可以为每个产品特性组合创建源集目录。例如,您可以创建 Java 源文件并将其添加到 src/demoMinApi24/java/ 目录中,这样 Gradle 就只会在编译同时对应这两种产品特性的变体时才使用这些源文件。您为产品特性组合创建的源集的优先级高于属于各个产品特性的源集。

        过滤变体

        Gradle 会为您配置的产品特性和版本类型的每种可能组合创建编译变体。但是,某些编译变体可能并不是您需要的,或者在项目上下文中没有意义。您可以通过在模块级 build.gradle 文件中创建变体过滤器来移除某些编译变体配置。

        以上一部分中的编译配置为例,假设您打算让“demo”版应用仅支持 API 级别 23 及更高级别。您可以使用 variantFilter 代码块过滤掉所有将“minApi21”和“demo”产品特性组合在一起的编译变体配置:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        android {
        ...
        buildTypes {...}

        flavorDimensions "api", "mode"
        productFlavors {
        demo {...}
        full {...}
        minApi24 {...}
        minApi23 {...}
        minApi21 {...}
        }

        variantFilter { variant ->
        def names = variant.flavors*.name
        // To check for a certain build type, use variant.buildType.name == "<buildType>"
        if (names.contains("minApi21") && names.contains("demo")) {
        // Gradle ignores any variants that satisfy the conditions above.
        setIgnore(true)
        }
        }
        }

        维度回退

        情况1:app中有某个build type但module中没有。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        // In the app's build.gradle file.
        android {
        buildTypes {
        debug {}
        release {}
        staging {
        // 下面[]中的qa、debug、release是module中配置的buildType,必须含有其中一个或更多,
        // 若module中buildType没有staging,gradle会根据matchingFallbacks的配置,
        // 依次按顺序去匹配
        // 注意:module与module之间存在依赖关系的话,也要在特定的build types中指定匹配关系
        matchingFallbacks = ['qa', 'debug', 'release']
        }
        }
        }

        注意:module中有但app中没有的build type是不会报错的,因为gradle插件根本不会去module中请求build type。

        情况2:在app和它的module中都有同一个维度(比如:flavorDimensions ‘tier’),但你的app有的flavors在module中没有。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        flavorDimensions 'tier'
        productFlavors {
        paid {
        // 因为依赖app的module在'tier'维度下也有'paid'这个flavor,所以你不用去管,
        // gradle会自动为你匹配
        dimension 'tier'
        }
        free {
        // 因为module在'tier'维度下没有'free'这个flavor,所以需要指定matchingFallbacks
        // 让gradle知道怎么去匹配
        // 像下面这样配置,gradle会按顺序依次去匹配module中'tier'维度下的flavor,
        // 直到匹配到,否则会报错
        matchingFallbacks = ['demo', 'trial']
        }
        }

        注意:对于在同一个维度下,module中有的flavors但app中没有是不会报错的,因为gradle插件根本不会去module中请求flavors。

        情况3:module中有某个dimension维度,但app中没有。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        // In the app's build.gradle file.
        android {
        defaultConfig{
        // 下面这句话告诉gradle,当遇到一个module中有个app中没有的'minApi'维度时,
        // 它应该按照下面这个顺序去匹配module中这个维度的flavors
        missingDimensionStrategy 'minApi', 'minApi18', 'minApi23'
        // 若其他module中还有更多app中没有的维度,你必须为所有的维度定义回退策略
        missingDimensionStrategy 'abi', 'x86', 'arm64'
        }
        flavorDimensions 'tier'
        productFlavors {
        free {
        dimension 'tier'
        // 你可以在一个特定的flavor中覆盖defaultConfig的配置
        missingDimensionStrategy 'minApi', 'minApi23', 'minApi18'
        }
        paid { }
        }
        }

        注意:当一个维度app中有但module中没有的时候是不会报错,因为gradle插件只会匹配已经在module中存在的维度,比如module中没有abi这个维度,当app为freeX86Debug时,你的module就用freeDebug。

        情况4:若module中没有某个dimension,则app不需要在这个dimension下做任何处理。

        创建源集

        创建源集

        默认情况下,Android Studio 会为您希望在所有编译变体之间共享的所有内容创建 main/ 源集和目录。但是,您可以创建新的源集来精确控制 Gradle 为特定版本类型、产品特性(以及使用类型维度时的产品特性组合)和编译变体编译和打包的文件。例如,您可以在 main/ 源集中定义基本功能,并使用产品特性源集来为不同客户端更改应用的品牌,或仅为使用“debug”版本类型的编译变体添加特殊权限和日志记录功能。

        Gradle 要求您以某种类似于 main/ 源集的方式组织源集文件和目录。例如,Gradle 要求将“debug”版本类型特有的 Java 类文件放在 src/debug/java/ 目录中。

        可通过 gradle sourceSets 查看不同变体期望的源集路径;可通过Android Studio自带功能创建源集。

        更改默认源集配置

        可以使用sourceSets修改默认源集路径。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        android {
        ...
        sourceSets {
        // Encapsulates configurations for the main source set.
        main {
        // Changes the directory for Java sources. The default directory is
        // 'src/main/java'.
        java.srcDirs = ['other/java']

        // If you list multiple directories, Gradle uses all of them to collect
        // sources. Because Gradle gives these directories equal priority, if
        // you define the same resource in more than one directory, you get an
        // error when merging resources. The default directory is 'src/main/res'.
        res.srcDirs = ['other/res1', 'other/res2']

        // Note: You should avoid specifying a directory which is a parent to one
        // or more other directories you specify. For example, avoid the following:
        // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
        // You should specify either only the root 'other/res1' directory, or only the
        // nested 'other/res1/layouts' and 'other/res1/strings' directories.

        // For each source set, you can specify only one Android manifest.
        // By default, Android Studio creates a manifest for your main source
        // set in the src/main/ directory.
        manifest.srcFile 'other/AndroidManifest.xml'
        ...
        }

        // Create additional blocks to configure other source sets.
        androidTest {

        // If all the files for a source set are located under a single root
        // directory, you can specify that directory using the setRoot property.
        // When gathering sources for the source set, Gradle looks only in locations
        // relative to the root directory you specify. For example, after applying the
        // configuration below for the androidTest source set, Gradle looks for Java
        // sources only in the src/tests/java/ directory.
        setRoot 'src/tests'
        ...
        }
        }
        }

        使用源集编译

        可以使用源集目录来添加只希望与某些配置打包在一起的代码和资源。例如,如果您要编译“demoDebug”这个变体(“demo”产品特性和“debug”版本类型的混合产物),则 Gradle 会查看这些目录,并为它们指定以下优先级:

      34. src/demoDebug/(编译变体源集)
      35. src/debug/(版本类型源集)
      36. src/demo/(产品特性源集)
      37. src/main/(主源集)
      38. 注意:如果您结合使用多个产品特性,那么这些产品特性的优先级由它们所属的类型维度决定。使用 android.flavorDimensions 属性列出类型维度时,属于您列出的第一个类型维度的产品特性的优先级高于属于第二个类型维度的产品特性,依此类推。此外,您为产品特性组合创建的源集的优先级高于属于各个产品特性的源集。

        上面列出的顺序决定了 Gradle 组合代码和资源时哪个源集的优先级更高。由于 demoDebug/ 源集目录可能包含该编译变体特有的文件,因此,如果 demoDebug/ 包含在 debug/ 中也定义了的文件,则 Gradle 会使用 demoDebug/ 源集中的文件。类似地,Gradle 会为版本类型和产品特性源集中的文件提供比 main/ 中的相同文件更高的优先级。在应用以下编译规则时,Gradle 会考虑这种优先顺序:

      39. java/ 目录中的所有源代码将一起编译以生成单个输出。
        注意:对于给定的编译变体,如果 Gradle 遇到两个或更多个源集目录定义了同一个 Java 类的情况,则会抛出编译错误。例如,在编译调试 APK 时,您不能同时定义 src/debug/Utility.java 和 src/main/Utility.java。这是因为 Gradle 在编译过程中会查看这两个目录并抛出“重复类”错误。如果您要为不同的版本类型使用不同版本的 Utility.java,则可以让每个版本类型定义各自的文件版本,而不是将其包含在 main/ 源集中。
      40. 所有清单都将合并为一个清单,优先级将按照上面列出的顺序提供。也就是说,版本类型的清单设置会替换产品特性的清单设置,依此类推。
      41. 同样,values/ 目录中的文件也会合并在一起。如果两个文件(如两个 strings.xml 文件)的名称相同,将按照上面列表中的顺序指定优先级。也就是说,在版本类型源集的文件中定义的值会重写在产品特性的同一文件中定义的值,依此类推。
      42. res/ 和 asset/ 目录中的资源会打包在一起。如果在两个或更多个源集中定义了同名的资源,将按照上面列表中的顺序指定优先级。
      43. 最后,在编译 APK 时,Gradle 会为库模块依赖项随附的资源和清单指定最低优先级。
      44. 声明依赖项

        可以为特定编译变体或测试源集配置依赖项,方法是在 Implementation 关键字前面加上编译变体或测试源集的名称作为前缀,如以下示例所示。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        dependencies {
        // Adds the local "mylibrary" module as a dependency to the "free" flavor.
        freeImplementation project(":mylibrary")

        // Adds a remote binary dependency only for local tests.
        testImplementation 'junit:junit:4.12'

        // Adds a remote binary dependency only for the instrumented test APK.
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        }

        配置签名设置

        除非您明确定义此版本的签名配置,否则 Gradle 不会为该版本的 APK 签名。您可以轻松创建发布密钥并使用 Android Studio 为发布版本类型签名。要使用 Gradle 编译配置为您的发布版本类型手动配置签名,请执行以下操作:

      45. 创建一个密钥库。密钥库是一个包含一组私钥的二进制文件。您必须将密钥库保存在安全可靠的地方。
      46. 创建一个私钥。私钥代表将通过应用识别的实体,如个人或公司。
      47. 将签名配置添加到模块级 build.gradle 文件中:
      48. 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        android {
        ...
        defaultConfig {...}
        signingConfigs {
        release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
        }
        }
        buildTypes {
        release {
        ...
        signingConfig signingConfigs.release
        }
        }
        }

        注意:在编译文件中添加发布密钥和密钥存储区的密码并不是一种好的安全做法。作为替代方案,您可以配置编译文件以从环境变量获取这些密码,或让编译流程提示您输入这些密码。

        要从环境变量获取这些密码,请编写以下代码:

        1
        2
        storePassword System.getenv("KSTOREPWD")
        keyPassword System.getenv("KEYPWD")

        要让编译流程在您要从命令行调用此编译时提示您输入这些密码,请编写以下代码:

        1
        2
        storePassword System.console().readLine("\nKeystore password: ")
        keyPassword System.console().readLine("\nKey password: ")

        警告:请将密钥库和私钥保存在安全可靠的地方,并确保您为其创建了安全的备份。如果您将应用发布到 Google Play,随后丢失了用于为应用签名的密钥,那么您将无法向您的应用发布任何更新,因为您必须始终使用相同的密钥为应用的所有版本签名。

        注意:当buildType.debug中没有指定signingConfig时,即使productFlavors中提供了签名配置,也会默认使用Android Studio提供的签名,因此如果要使debug下的flavor签名生效,需要指定debug的signingConfig为null,如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        def flavorSigns = [
        "base" : readSigningConfig(file('../signing1.properties')),
        "flavorA": readSigningConfig(file('../signing2.properties')),
        ]

        android {
        signingConfigs {
        flavorSigns.forEach { key, value ->
        "$key" {
        keyAlias value.keyAlias
        keyPassword value.keyPassword
        storeFile value.storeFile
        storePassword value.storePassword
        }
        }
        }
        defaultConfig {
        signingConfig signingConfigs.base
        }
        buildTypes {
        release {
        // ...
        }
        debug {
        // ...
        // Android Studio adds a default signingConfig for debug builds and we can remove it by passing null.
        // By doing this, I delegate the signingConfig to the product flavors.
        signingConfig null
        }
        }
        flavorDimensions "version", "channel"

        productFlavors {
        stable {
        dimension "version"
        }
        alpha {
        dimension "version"
        }
        base {
        dimension "channel"
        }
        flavorA {
        dimension "channel"
        signingConfig signingConfigs.flavorA
        }
        }
        }

        构建多应用

        构建多应用

        屏幕密度

        以下为compatibleScreens列出的每个屏幕密度生成单独的APK,但ldpi,xxhdpi和xxxhdpi除外:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        android {
        // ...
        splits {
        // Configures multiple APKs based on screen density.
        density {
        // Configures multiple APKs based on screen density.
        enable true
        // Specifies a list of screen densities Gradle should not create multiple APKs for.
        exclude "ldpi", "xxhdpi", "xxxhdpi"
        // Specifies a list of compatible screen size settings for the manifest.
        compatibleScreens 'small', 'normal', 'large', 'xlarge'
        }
        }
        }
      49. enable:如果将此元素设置为true,Gradle会根据您定义的屏幕密度生成多个APK。默认值为false。

      50. exclude:指定以逗号分隔的密度列表,Gradle不应为其生成单独的APK。

      51. reset():清除默认的屏幕密度列表,仅在与include元素组合时使用, 以指定要添加的密度。

      52. include:指定Gradle应为其生成APK的密度列表。只能结合使用reset()来指定密度的确切列表。

      53. compatibleScreens:指定兼容屏幕尺寸的逗号分隔列表,这会为每个APK在manifest中注入一个匹配的 <compatible-screens> 节点。此设置提供了在同一build.gradle中管理屏幕密度和屏幕大小的便捷方法。但是,使用 <compatible-screens> 限制了应用程序可以使用的设备类型。

        1
        2
        reset()  // Clears the default list from all densities to no densities.
        include "ldpi", "xxhdpi" // Specifies the two densities we want to generate APKs for.

        因为基于屏幕密度的每个APK都包含 <compatible-screens> 标记,其中包含有关APK支持的屏幕类型的特定限制,即使您发布了多个APK,某些新设备也无法匹配您的多个APK过滤器。因此,Gradle始终会生成一个额外的通用APK,其中包含所有屏幕密度的资源,并且不包含 <compatible-screens> 标记。您应该发布此通用APK以及每个密度的APK,以便为与APK兼容的设备提供后备兼容的 <compatible-screens> 标记。

        ABI

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        android {
        // ...
        splits {

        // Configures multiple APKs based on ABI.
        abi {

        // Enables building multiple APKs per ABI.
        enable true

        // By default all ABIs are included, so use reset() and include to specify that we only
        // want APKs for x86 and x86_64.

        // Resets the list of ABIs that Gradle should create APKs for to none.
        reset()

        // Specifies a list of ABIs that Gradle should create APKs for.
        include "x86", "x86_64"

        // Specifies that we do not want to also generate a universal APK that includes all ABIs.
        universalApk false
        }
        }
        }

        ABI 包含以下信息:

      54. 机器代码应使用的 CPU 指令集。
      55. 运行时内存存储和加载的字节顺序。
      56. 可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。
      57. 用于解析内容与系统之间数据的各种约定。这些约定包括对齐限制,以及系统如何使用堆栈和在调用函数时注册。
      58. 运行时可用于机器代码的函数符号列表 - 通常来自非常具体的库集。
      59. enable:如果您将此元素设置为true,Gradle会根据您定义的ABI生成多个APK。默认值是false
      60. exclude:指定用逗号分隔的ABI的名单不生成单独的APK。
      61. reset:清除ABI的默认列表。仅在与include元素结合使用时才使用, 以指定要添加的ABI。
      62. include:指定Gradle应为其生成APK的ABI的逗号分隔列表。只能结合使用reset()来指定ABI的确切列表。
      63. universalApk:如果true,除了per-ABI APK,Gradle还生成通用APK。通用APK包含单个APK中所有ABI的代码和资源。默认值是false。请注意,该选项仅在该splits.abi块中可用。当根据屏幕密度构建多个APK时,Gradle始终会生成一个通用APK,其中包含用于所有屏幕密度的代码和资源。
      64. 在Gradle 3.1.0及更高版本中不再默认生成支持mips, mips64, 和armeabi的apk,因为 NDK r17 及更高版本不再支持这些abi。因此如果使用Gradle版本低于3.1.0,NDK高于r17,则会报错。

        配置版本

        默认生成的多个apk的版本信息是一样的,但是GP不允许同一应用的多apk拥有相同的版本信息,因此需要为其生成不同的版本。

        如果您的构建包含通用APK,则应为其分配一个低于任何其他APK的版本代码。 由于Google Play商店会安装与目标设备兼容且版本编号最高的应用版本,因此将较低版本的代码分配给通用APK可确保Google Play商店尝试安装其中一个APK,然后再回到通用版本APK。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        android {
        ...
        defaultConfig {
        ...
        versionCode 4
        }
        splits {
        ...
        }
        }

        // Map for the version code that gives each ABI a value.
        ext.abiCodes = ['armeabi-v7a':1, 'x86':2, 'x86_64':3]

        // For per-density APKs, create a similar map like this:
        // ext.densityCodes = ['mdpi': 1, 'hdpi': 2, 'xhdpi': 3]

        import com.android.build.OutputFile

        // For each APK output variant, override versionCode with a combination of
        // ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
        // is equal to defaultConfig.versionCode. If you configure product flavors that
        // define their own versionCode, variant.versionCode uses that value instead.
        android.applicationVariants.all { variant ->

        // Assigns a different version code for each output APK
        // other than the universal APK.
        variant.outputs.each { output ->

        // Stores the value of ext.abiCodes that is associated with the ABI for this variant.
        def baseAbiVersionCode =
        // Determines the ABI for this variant and returns the mapped value.
        project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))

        // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
        // the following code does not override the version code for universal APKs.
        // However, because we want universal APKs to have the lowest version code,
        // this outcome is desirable.
        if (baseAbiVersionCode != null) {

        // Assigns the new version code to versionCodeOverride, which changes the version code
        // for only the output APK, not for the variant itself. Skipping this step simply
        // causes Gradle to use the value of variant.versionCode for the APK.
        output.versionCodeOverride =
        baseAbiVersionCode * 1000 + variant.versionCode
        }
        }
        }

        格式化apk名

        1
        2
        3
        4
        5
        6
        7
        android {
        applicationVariants.all { variant ->
        variant.outputs.all {
        outputFileName = "${variant.applicationId}-${variant.name}-${variant.versionName}.apk"
        }
        }
        }

        Manifest合并

        合并优先级

        合并工具会根据每个清单文件的优先级按顺序合并,将所有清单文件组合到一个文件中。

        有三种基本的清单文件可以互相合并,它们的合并优先级如下(按优先级由高到低的顺序):

      65. 编译变体的清单文件
        如果您的变体有多个源集,则其清单优先级如下:

      66. 编译变体清单(如 src/demoDebug/)
      67. 版本类型清单(如 src/debug/)
      68. 产品类型清单(如 src/demo/)
        如果您使用的是类型维度,则清单优先级将与每个维度在 flavorDimensions 属性中的列示顺序(按优先级由高到低的顺序)对应。
      69. 应用模块的主清单文件

      70. 所包含的库中的清单文件
        如果您有多个库,则其清单优先级与依赖顺序(库出现在 Gradle dependencies 块中的顺序)匹配。例如,先将库清单合并到主清单中,然后再将主清单合并到编译变体清单中。

        注:build.gradle 文件中的编译配置将替换合并后的清单文件中的所有对应属性。例如,build.gradle 文件中的 minSdkVersion 将替换 <uses-sdk> 清单元素中的匹配属性。为了避免混淆,您只需省去 <uses-sdk> 元素并在 build.gradle 文件中定义这些属性。

        合并冲突启发式算法

        合并工具可以在逻辑上将一个清单中的每个 XML 元素与另一个清单中的对应元素相匹配。如果优先级较低的清单中的某个元素与优先级较高的清单中的任何元素都不匹配,则会将该元素添加到合并后的清单。不过,如果有匹配的元素,则合并工具会尝试将每个元素的所有属性组合到同一元素中。如果该工具发现两个清单包含相同的属性,但值不同,则会发生合并冲突。

        高优先级属性 低优先级属性 属性的合并结果 没有值(使用默认值) 冲突错误 - 您必须添加合并规则标记

        不过,在某些情况下,合并工具会采取其他行为方式以避免合并冲突:

      71. <manifest> 元素中的属性绝不会合并在一起 - 仅使用优先级最高的清单中的属性。
      72. <uses-feature> <uses-library> 元素中的 android:required 属性使用 OR 合并,这样一来,如果发生冲突,系统将应用 “true” 并始终包含某个清单所需的功能或库。
      73. <uses-sdk> 元素中的属性始终使用优先级较高的清单中的值,但以下情况除外: