[关闭]
@TryLoveCatch 2022-06-14T09:38:13.000000Z 字数 14410 阅读 2676

Android知识体系之Gradle基础知识

Android知识体系


参考

看完这一系列,彻底搞懂 Gradle
关于Android Gradle你需要知道这些(1)
关于Android Gradle你需要知道这些(2)
10分钟了解Android项目构建流程
关于Android Gradle你需要知道这些(3)
关于Android Gradle你需要知道这些(4)
刘望舒-Gradle-3篇

任玉刚3篇

dasu三篇
dasu其他gradle相关

深入理解Android之Gradle
你真的了解 Gradle 吗?

API查找

groovy-jdk
groovy
Gradle DSL
Android DSL

Android 项目中的 Gradle

你真的了解 Gradle 吗?

Android DSL

gradle/wrapper 目录

gradle-wrapper.properties中各属性的含义
gradle wrapper分析

我们要开发 Java 程序,本地需要配置 JDK 环境,要开发 Android 程序,需要配置 SDK 一样,想要借助 Gradle 来构建项目,那么按理说本地也需要配置相关的 Gradle 环境才对。
而我们之所以可以省掉这一步,就是 gradle/wrapper 这个目录下的文件的作用了。

Gradle内置了wrapper task帮助我们自动生成wrapper所需de目录文件,我们在一个项目的根目录下面,执行命令即可:

  1. gradle wrapper

生成的文件如下:

  1. |-gradle
  2. |-wrapper
  3. |-gradle-wrapper.jar
  4. |-gradle-wrapper.properties
  5. |-gradlew
  6. |-gradlew.bat

先来看一下gradle/wrapper的目录结构

  1. gradle-wrapper.propertes //gradle-wrapper 配置文件
  2. gradle-wrapper.jar //gradle-wrapper 核心 jar

gradle-wrapper.propertes

来看下gradle-wrapper.properties的内容

  1. #Wed Sep 06 14:36:34 HKT 2017
  2. distributionBase=GRADLE_USER_HOME
  3. distributionPath=wrapper/dists
  4. zipStoreBase=GRADLE_USER_HOME
  5. zipStorePath=wrapper/dists
  6. distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip

distributionUrl

重点说一下这个,gradle-wrapper.propertes里面最重要的就是这一句,指明了当前这个项目要使用哪个版本的 Gradle 来构建。
我们在 Android Studio 的 File -> Project Structure -> Project 里配置的 Gradle Version,最终改变的其实就是上述文件里最后一行的 Gradle 版本属性值:

第一行是gradle版本
第二行是android gradle插件版本
两者的对应关系见官方文档

官方说了,提供了 gradle/wrapper 这种方式,可以让你特别灵活的进行配置,想换个 Gradle 版本来构建项目,只需要修改这个配置文件的 Gradle 版本属性值即可,当然也可以直接通过 AS 提供的 UI 界面操作,结果都一样。

由于 Gradle 更新换代特别快,而且新的大版本经常都会提供很多新特性,这就导致了在 clone Github 上一些开源项目到本地构建时经常有报错的问题,本质原因就是因为它使用的 Gradle 版本跟你本地不一样,而由于有堵巨墙的原因,导致一直没法成功下载它配置的 Gradle 版本,进而就无法构建项目,而报错了。

网上说的一些解决方案是让你手动去修改 gradle-wrapper.properties 文件里的 Gradle 版本,改成你本地的版本,但我觉得这种方法不一定适用,这取决于那个项目中是否有用到一些新特性,以及你本地的 Gradle 版本是否兼容项目中用到的 Gradle 新特性。

通常来说,如果你本地的 Gradle 比克隆的项目的 Gradle 版本高的话,那么这种直接修改项目的 Gradle 版本方式应该是可行的,那么怎么知道你本地都有哪些 Gradle 版本呢:

就是上面gradle-wrapper.propertes里面配置的路径,在MAC下面,就是~/.gradle/wrapper/dists,只要你在 gradle-wrapper.properties 修改了 Gradle 的版本号,那么当你在构建项目时,就会先到你电脑的这个路径下查找相对应版本的 Gradle,如果可用,则直接进行构建项目任务,如果不存在,那么就会自动去下载对应版本的 Gradle。

关于gradle版本

gradle 有 3 种版本:

gradle.priperties

简而言之,就是一个配置全局的管理文件。
配置 gradle 运行环境的文件,比如配置 gradle 运行模式,运行时 jvm 虚拟机的大小。

举个栗子:
一般大型的项目都是拆分进行模块化开发的,这时候每个模块在合并的时候容易有冲突(假如版本冲突),或者需要改版本的时候比较麻烦,需要一个一个地方去改,这时候配置全局的 gradle.properties 文件就显得异常有帮助了。

例如:

  1. # Project-wide Gradle settings.
  2. #添加ndk支持(按需添加)
  3. android.useDeprecatedNdk=true
  4. # 应用版本名称
  5. VERSION_NAME=1.0.0
  6. # 应用版本号
  7. VERSION_CODE=100
  8. # 支持库版本
  9. SUPPORT_LIBRARY=24.2.1
  10. # MIN_SDK_VERSION
  11. ANDROID_BUILD_MIN_SDK_VERSION=14
  12. # TARGET_SDK_VERSION
  13. ANDROID_BUILD_TARGET_SDK_VERSION=24
  14. # BUILD_SDK_VERSION
  15. ANDROID_BUILD_SDK_VERSION=24
  16. # BUILD_TOOLS_VERSION
  17. ANDROID_BUILD_TOOLS_VERSION=24.0.3

这样就为项目配置了全局属性,其他模块就可以直接使用了,需要改动的时候只需要在这个全局的配置文件中一键修改即可,如下:

  1. android {
  2. compileSdkVersion project.ANDROID_BUILD_SDK_VERSION as int
  3. buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
  4. defaultConfig {
  5. applicationId project.APPLICATION_ID
  6. versionCode project.VERSION_CODE as int
  7. versionName project.VERSION_NAME
  8. minSdkVersion project.ANDROID_BUILD_MIN_SDK_VERSION as int
  9. targetSdkVersion project.ANDROID_BUILD_TARGET_SDK_VERSION as int
  10. }
  11. }
  12. dependencies {
  13. compile fileTree(include: ['*.jar'], dir: 'libs')
  14. //这里注意是双引号
  15. compile "com.android.support:appcompat-v7:${SUPPORT_LIBRARY}"
  16. compile "com.android.support:design:${SUPPORT_LIBRARY}"
  17. compile "com.android.support:recyclerview-v7:${SUPPORT_LIBRARY}"
  18. compile "com.android.support:support-annotations:${SUPPORT_LIBRARY}"
  19. compile "com.android.support:cardview-v7:${SUPPORT_LIBRARY}"
  20. compile "com.android.support:support-v4:${SUPPORT_LIBRARY}"
  21. }

这里可以看到有些属性后面有 as int ,这是因为原本的配置应该都是默认是字符串,然后用 as int 就可以轻松转换成 int 类型了。

local.properties

  1. ## This file is automatically generated by Android Studio.
  2. # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
  3. #
  4. # This file must *NOT* be checked into Version Control Systems,
  5. # as it contains information specific to your local configuration.
  6. #
  7. # Location of the SDK. This is only used by Gradle.
  8. # For customization when using a Version Control System, please read the
  9. # header note.
  10. #Wed May 30 15:32:23 CST 2018
  11. ndk.dir=/Users/xxx/Library/Android/sdk/ndk-bundle
  12. sdk.dir=/Users/xxx/Library/Android/sdk

Android Studio 自动创建的工程中,IDE 自动配备了本地配置文件,并且设置了 sdk.dir 属性。也就是配置了 Android_Home 环境变量,所以 IDE 能够自动生成这个配置文件。一般情况都不用修改这个配置文件,除非你一定要重新制定一个 SDK 路径。

gradlew.bat & gradlew

gradlew 文件和 gradlew.bat 文件,两份没有什么差别,它们都是脚本文件,区别只是一个是 shell 脚本,一个是批处理脚本,那么自然一个是用来在 Linux 上运行,一个在 Windows 上运行。

它借助了 gradle/wrapper 目录下的 gradle-wrapper.jar 文件,并借助了 java 命令,提供了可让我们直接以命令行形式运行一些相应的 gradle 指令,而这些指令在 gradle-wrapper.jar 文件中都提供了相应的实现。

setting.gradle

setting.gradle 文件通常是当项目涉及到多 Module 的场景。

只有在 setting.gradle 中 include 的 Module,才会被加入构建中,也就是说,如果一个项目文件夹内,包含了很多子工程,但如果没在 setting.gradle 中将这些子工程 include 进来的话, 这些 Module 是不会参与进构建的。

  1. include ':app'
  2. include ':library'
  3. include ':other'
  4. project(':other').projectDir = file('other/app')

如果子工程的这些 Module 都直接放在了项目根目录中,那么 setting.gradle 中只需要写 include 就可以了,那如果这些子工程是放在别的地方,那么也可以通过修改 project().projectDir 来指定子工程的具体路径,也就是说,所有的 Module 并不一定需要全部集中放在同一个项目内。

例如上面的other,它的结构可能是:

  1. - MyApp
  2. |-- app
  3. |-- library
  4. |-- other
  5. |--- app
  6. -- setting.gradle

build.gradle

每一个project都会有一个build文件,所以root project也会有一个。

子工程下的build.gradle

一个项目中可能存在多个子工程,每个子工程构建都应该是相互独立的,也就是说,每个子工程都可以根据自己的需要,配置各种依赖,插件等。
那么,Gradle 是如何分开来管理每个子工程的构建任务的呢?

这就是 build.gradle 文件的作用了,所以你会发现,每个子工程,也就是每个 Module 都会有一个 build.gradle 文件,Gradle 就是以这个文件为根据来构建这个 Module。

根目录下的build.gradle

那么,如果有些配置项,在所有的子工程中都是一致的话,如果在每个子工程里都去重复粘贴的话,当这个共同的配置项需要发生变化时,维护起来会非常麻烦,这也就是为什么根目录下面还会有一个 build.gradle 文件。

根目录下的这个 build.gradle 是统筹全局的,在这里,你可以配置一些所有工程共同的配置项,比如 Android Gradle 的版本,依赖库的仓库地址这些所有工程的共同配置项。

也就是说,其实将根目录下的 build.gradle 文件里的内容移到每一个工程下的 build.gradle 里,也是可行的。但没必要这样做,吃饱了撑着。

buildscript & allprojects
  1. buildscript {
  2. repositories {
  3. maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
  4. }
  5. dependencies {
  6. classpath 'com.android.tools.build:gradle:3.0.1'
  7. }
  8. }
  9. allprojects {
  10. repositories {
  11. maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
  12. maven {
  13. url "file:///${rootProject.projectDir}/extras/m2repository/"
  14. }
  15. }
  16. dependencies {
  17. ...
  18. }
  19. }

defaultConfig

https://juejin.im/post/5c70a1f56fb9a04a0e2dd008
https://blog.csdn.net/shijianduan1/article/details/83410267

manifestPlaceholders = [hostName:"www.example.com",post:"8080"]

productFlavors

https://blog.csdn.net/u012149399/article/details/88124473

app
moduleA
moduleB

app如果有flavor不要求每一个module都有,但是如果有一个module有flavor,所有依赖这个moudle的app或者module都需要区分flavor

实战

依赖包冲突

com.android.support冲突的解决办法

我们经常遇到com.android.support包冲突

  1. All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes

大致意思就是com.android.support的包版本号要保持一致,但是可能我们自己新建的项目的com.android.support包版本号要高一些,一些第三方的库的com.android.support可能没有及时更新support库,就会出现这个错误。

所以说根据这个原因,解决方法就有三个:

第二种解决方式

既然要排除依赖,那么就需要知道,那些第三方依赖了com.android.support,以及其依赖版本号

  1. gradlew -q app:dependencies

该命令会将app中所依赖的库展示出来:

所以排除这个库即可:

  1. api ("com.alibaba:arouter-api:1.3.1") {
  2. exclude group: 'com.android.support'
  3. }
  4. api ("com.alibaba:arouter-api:1.3.1") {
  5. exclude group: 'com.android.support', module: 'support-v4'
  6. }

一般来说,在我们自己创建library给别人使用时,如果需要依赖com.android.support的话,建议用compileOnly的方式依赖,这样只会在编译时有效,不会参与打包。

  1. compileOnly 'com.android.support:appcompat-v7:26.1.0'
  2. compileOnly 'com.android.support:design:26.1.0'
  3. compileOnly 'com.android.support:support-vector-drawable:26.1.0'

第三种解决方式

在存在冲突的module中的build.gradle文件中加入下面代码:

  1. configurations.all {
  2. resolutionStrategy.eachDependency { DependencyResolveDetails details ->
  3. def requested = details.requested
  4. if (requested.group == 'com.android.support') {
  5. if (!requested.name.startsWith("multidex")) {
  6. details.useVersion '28.0.0'
  7. }
  8. }
  9. }
  10. }

原理就是通过遍历所有依赖,并修改指定库的版本号

  • requested.group == 'com.android.support'
    com.android.support表示要修改的依赖库
  • details.useVersion '28.0.0'
    28.0.0表示要修改的版本号

拾遗

壹 根据环境引入不同aar

AndroidStudio根据环境打包不同的aar文件

  1. MyProject
  2. - app
  3. - build.gradle
  4. - demolib
  5. - xxx_debug.aar
  6. - xxx_release.aar
  7. - build.gradle
  8. - demolib.iml

build.gradle(Module:demolib)

  1. configurations.maybeCreate("default")
  2. artifacts.add("default", file('xxx_release.aar'))
  3. configurations.maybeCreate("debug")
  4. artifacts.add("debug", file('xxx_debug.aar'))

build.gradle(Module:app)

  1. dependencies {
  2. implementation project(':demolib')
  3. debugImplementation project(path: ':demolib', configuration: 'debug')
  4. }

贰 不新建module引入aar

Android依赖导入全攻略

当我们需要引入aar的时候,可以像上面这样新建一个module,也可以采用这种方式

  1. module-x
  2. - libs
  3. - xxx.aar
  4. - src
  5. build.gradle

我们把aar文件放到了libs下面,我们还需要修改build.gradle

  1. android {
  2. // ...
  3. repositories {
  4. flatDir { dirs 'libs' }
  5. }
  6. }
  7. dependencies {
  8. implementation fileTree(dir: 'libs', include: ['*.jar'])
  9. api(name: 'onsdk_all', ext: 'aar')
  10. }

如果是在library的module里面这样写,其他applicaiton module引入的时候会有问题:

  1. module-library
  2. - libs
  3. - xxx.aar
  4. - src
  5. build.gradle
  6. module-application
  7. - src
  8. build.gradle

并且module-application是依赖module-library的:

  1. implementation project(':module-library')

这个时候,编译会报错,提示xxx.aar找不到,module-application也需要引入xxx.aar,如下:

  1. module-library
  2. - libs
  3. - xxx.aar
  4. - src
  5. build.gradle
  6. module-application
  7. - libs
  8. - xxx.aar
  9. - src
  10. build.gradle

然后module-application的build.gradle也需要修改:

  1. android {
  2. // ...
  3. repositories {
  4. flatDir { dirs 'libs' }
  5. }
  6. }
  7. dependencies {
  8. implementation fileTree(dir: 'libs', include: ['*.jar'])
  9. api(name: 'onsdk_all', ext: 'aar')
  10. }

叁 DuplicateFileException

记一些以前不知道的 Gradle 配置
我们经常遇到这样的问题:

  1. com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK lib/armeabi/libgnustl_shared.so
  2. File1: /Users/lipeng/.android/build-cache/1352369e8237b54bc36dca29ad3df6710a0e004b/output/jni
  3. File2: /Users/lipeng/work/workspace/qcs.android.map/3dmap-navibaidu/build/intermediates/bundles/naviDebug/jni

特别是在使用第三方sdk的时候,那么如何解决呢?

  1. android {
  2. // ...
  3. packagingOptions {
  4. exclude 'META-INF/rxjava.properties'
  5. pickFirst 'org/apache/http/entity/mime/version.properties'
  6. pickFirst 'lib/armeabi/libgnustl_shared.so'
  7. }
  8. }

肆 assemble命令

GRADLE命令简单学习

一般用法(assemble + Build Type) :

  1. // 编译并打Debug包
  2. ./gradlew assembleDebug
  3. // 编译并打Release的包
  4. ./gradlew assembleRelease

可能有多个可执行module(module + assemble):

  1. // 名字为app的module,编译并打Debug包
  2. ./gradlew :app:assembleDebug

assemble + productFlavors :

  1. // 给wandoujia渠道编译并打Debug包和Release包
  2. ./gradlew assembleWandoujia

assemble + productFlavors + Build Type:

  1. // 打包wandoujia渠道的release版本
  2. ./gradlew assembleWandoujiaRelease

所以,根据上面的例子,我们得到的公式是:

  1. ./gradlew [:module][:]assemble[ProductFlavors][BuildType]

伍 gradle下载aar的缓存路径

  1. /Users/xxxx/.gradle/caches/modules-2/files-2.1

这个路径下面,就是gradle下载的所有aar了

陆 打aar包

假设我要打mylibrary包,那么:

点击右侧的assembleRelease,就会生成aar文件,位置在:

  1. mylibrary/build/outputs/aar/mylibrary-release.aar

还有两个前提:

柒 发布本地maven

Android studio中使用Maven发布本地仓库

假设工程结构如下:

  1. MyProject
  2. - app
  3. - build.gradle
  4. - sdk
  5. - build.gradle
  6. - build.gradle

方法一

  1. apply plugin: 'com.android.library'
  2. // 这里!!!!!
  3. apply plugin: 'maven'
  4. android {
  5. compileSdkVersion 28
  6. defaultConfig {
  7. minSdkVersion 16
  8. targetSdkVersion 28
  9. versionCode 1
  10. versionName "1.0"
  11. }
  12. buildTypes {
  13. release {
  14. minifyEnabled false
  15. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  16. }
  17. }
  18. }
  19. dependencies {
  20. implementation fileTree(dir: 'libs', include: ['*.jar'])
  21. }
  22. // 这里!!!!!
  23. // 本地maven仓库地址
  24. def localMavenRepo = 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath
  25. uploadArchives {
  26. repositories.mavenDeployer {
  27. // 本地仓库路径
  28. repository(url: localMavenRepo)
  29. // 唯一标识
  30. pom.groupId = "com.test.sdk"
  31. // 项目名称
  32. pom.artifactId = "sdk-name"
  33. // 版本号
  34. pom.version = "1.0.1"
  35. }
  36. }

主要需要注意上面注释的两处更改,像上面这样配置后,就会组装成这样:

  1. implementation 'com.test.sdk:sdk-name:1.0.1'

上面的这个硬编码了本地地址,不太好,还有一种更好的写法:

  1. uploadArchives {
  2. repositories {
  3. def localMavenRepo = mavenLocal().getUrl()
  4. mavenDeployer {
  5. // 本地仓库路径
  6. repository(url: localMavenRepo)
  7. // 唯一标识
  8. pom.groupId = "com.test.sdk"
  9. // 项目名称
  10. pom.artifactId = "sdk-name"
  11. // 版本号
  12. pom.version = "1.0.1"
  13. }
  14. }
  15. }

方法二

使用第三方插件
android-maven-gradle-plugin

  1. apply plugin: 'com.github.dcendents.android-maven'
  2. group = "com.test.sdk"
  3. version = "1.0.1"
  4. project.archivesBaseName = "sdk-name"
  5. android.defaultPublishConfig "baiduRelease"
  6. def hasAndroidPlugin() {
  7. return getPlugins().inject(false) { a, b ->
  8. def classStr = b.getClass().name
  9. def isAndroid = ("com.android.build.gradle.LibraryPlugin" == classStr) || ("com.android.build.gradle.AppPlugin" == classStr)
  10. a || isAndroid
  11. }
  12. }
  13. task sourcesJar(type: Jar) {
  14. if (hasAndroidPlugin()) {
  15. from android.sourceSets.main.java.srcDirs
  16. classifier = 'sources'
  17. } else {
  18. from sourceSets.main.allSource
  19. classifier = 'sources'
  20. }
  21. }
  22. artifacts {
  23. archives sourcesJar
  24. }
  25. def localMavenRepo = 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath
  26. uploadArchives {
  27. repositories {
  28. def localMavenRepo = mavenLocal().getUrl()
  29. mavenDeployer {
  30. // 本地仓库路径
  31. repository(url: localMavenRepo)
  32. }
  33. }
  34. }

使用本地maven

  1. MyProject
  2. - app
  3. - build.gradle
  4. - build.gradle
  1. // Top-level build file where you can add configuration options common to all sub-projects/modules.
  2. apply from: "config.gradle"
  3. buildscript {
  4. repositories {
  5. jcenter()
  6. // 这里!!!!!!
  7. mavenLocal()
  8. }
  9. dependencies {
  10. classpath 'com.android.tools.build:gradle:2.3.1'
  11. }
  12. }
  13. allprojects {
  14. repositories {
  15. jcenter()
  16. // 这里!!!!!!
  17. mavenLocal()
  18. }
  19. }
  20. task clean(type: Delete) {
  21. delete rootProject.buildDir
  22. }

加上mavenLocal()即可

  1. implementation 'com.test.sdk:sdk-name:1.0.1'

注意:如果是多productFlavors,那么需要指定具体的android.defaultPublishConfig "baiduRelease",这样才能发布成功

捌 版本号管理

主流来说,是3位或者4位:

一般来说采用3位即可。

玖 版本冲突

https://segmentfault.com/a/1190000021587501
https://blog.csdn.net/chengqiuming/article/details/103217889
对于 Maven 的自动处理传递性依赖版本冲突问题,是按最短路径和优先声明原则来处理.
而对于 Gradle 来说同样有着自动处理传递性依赖版本冲突问题的功能,只是 Gradle 是默认使用版本最高的.而针对一些特殊的需求还是需要使用手动解决.以下便是 Gradle 的手动处理版本冲突.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注