【Android】Gradle的Android插件

1.什么是Gradle的Android插件

Gradle核心思想(五)通俗易懂的Gradle插件讲解这篇文章中我们知道,Gradle有很多插件,为了支持Android项目的构建,谷歌为Gradle编写了Android插件,新的Android构建系统就是由Gradle的Android插件组成的,Gradle是一个高级构建工具包,它管理依赖项并允许开发者自定义构建逻辑。Android Studio使用Gradle wrapper来集成Gradle的Android插件。需要注意的是,Gradle的Android插件也可以独立于AndroidStudio运行。
Android的官方网站提到了新的Android构建系统主要有以下几个特点:

  • 代码和资源易于重用
  • 无论是针对多个apk发行版还是针对不同风格的应用程序,都可以很容易创建应用程序的多个不同版本。
  • 易于配置、扩展和自定义构建过程
  • 良好的IDE集成

Gradle的Android插件结合Android Studio成为了目前最为流行的Android构建系统。

2. Android Studio的模块类型和项目视图

Android Studio中的每个项目包含一个或多个含有源代码文件和资源文件的模块,这些模块可以独立构建、测试或调试,一个Android Studio的模块类型可以有以下几种:

Android应用程序模块
Android应用程序模块可能依赖于库模块,尽管许多Android应用程序只包含一个应用程序模块,构建系统会将其生成一个APK。

Android 库模块
Android库模块包含可重用的特定于Android的代码和资源,构建系统会将其生成一个AAR。

App 引擎模块
包含应用程序引擎集成的代码和资源。

Java 库模块
包含可重用的代码,构建系统会将其生成一个JAR包。

Android Studio3.3.2 中的Android项目视图如下所示。
Vep71s.png
所有构建文件在 Gradle Scripts 层级下显示,大概介绍下这些文件的用处。

  • 项目build.gradle:配置项目的整体属性,比如指定使用的代码仓库、依赖的Gradle插件版本等等。
  • 模块build.gradle:配置当前Module的编译参数。
  • gradle-wrapper.properites:配置Gradle Wrapper,可以查看Gradle核心思想(四)看似无用,实则重要的Gradle Wrapper这篇文章。
  • gradle.properties:配置Gradle的编译参数。具体配置见Gradle官方文档
  • settings.gradle:配置Gradle的多项目管理。
  • local.properties:一般用来存放该Android项目的私有属性配置,比如Android项目的SDK路径。

这篇文章主要介绍项目build.gradle和模块build.gradle。

3.项目build.gradle

我们新建一个Android项目,它的项目build.gradle的内容如下:

gradle

buildscript {
    repositories {
        google()
        jcenter()   
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.2' //1
    }
}

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

task clean(type: Delete) {
    delete rootProject.buildDir
}

注释1处配置依赖的Gradle插件版本,Gradle插件属于第三方插件,因此这里在buildscrip块中配置谷歌的Maven库和JCenter库,这样Gradle系统才能找到对应的Gradle插件。
如果使用google()not found: 'google()'错误,可以用如下代码替代:

Code

maven { url 'https://maven.google.com' }

如果你还不理解Gradle插件,可以查看Gradle核心思想(五)通俗易懂的Gradle插件讲解这篇文章。

4.模块build.gradle

新建一个Android项目,它的模块build.gradle的内容如下:

gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

4.1 Gradle的Android插件类型

apply引入的插件id为com.android.application,说明当前模块是一个应用程序模块,Gradle的Android插件有多个类型分别为:

  • 应用程序插件,插件id为com.android.application,会生成一个APK。
  • 库插件,插件id为com.android.library,会生成一个AAR,提供给其他应用程序模块用。
  • 测试插件,插件id为com.android.test,用于测试其他的模块。
  • feature插件,插件id为com.android.feature,创建Android Instant App时需要用到的插件。
  • Instant App插件,插件id为com.android.instantapp,是Android Instant App的入口。

4.2 Android块

Android块用于描述该Module构建过程中所用到的所有参数。

  • compileSdkVersion:配置编译该模块的SDK版本
  • buildToolsVersion:Android构建工具的版本

4.2.1 defaultConfig块

Android块中的defaultConfig块用于默认配置,常用的配置如下所示。

属性描述
applicationId指定App的包名
minSdkVersionApp最低支持的SDK版本
targetSdkVersion基于哪个SDK版本开发
versionCodeApp内部的版本号,用于控制App升级
versionNameApp版本名称,也就是发布的版本号
testApplicationId配置测试App的包名
testInstrumentationRunner配置单元测试使用的Runner,默认为android.test.InstrumentationTestRunner
proguardFileProGuard混淆所使用的ProGuard配置文件
proguardFiles同时配置多个ProGuard配置文件
signingConfig配置默认的签名信息

4.2.2 buildTypes块

buildTypes块用于配置构建不同类型的APK。
当我们新建一个项目时,在Android块已经默认配置了 buildTypes块:

gradle

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

在AS的Terminal中执行gradlew.bat build命令,会在该模块的build/outputs/apk目录中生成release和debug的APK,虽然只配置了release ,但release和debug是默认配置,即使我们不配置也会生成。也可以修改默认的release和debug,甚至可以自定义构建类型,比如:

gradle

buildTypes {
       release {
           minifyEnabled false
           proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       }
       debug {
           debuggable true
       }
       privitedebug{
           applicationIdSuffix ""
       }
   }

这时会在build/outputs/apk目录中生成release、debug、privitedebug的APK。
buildTypes块还可以配置很多属性,常用的配置如下所示。

属性描述
applicationIdSuffix配置applicationId的后缀
debuggable表示是否支持断点调试
jniDebuggable表示是否可以调试NDK代码
buildConfigField配置不同的开发环境,比如测试环境和正式环境
shrinkResources是否自动清理未使用的资源,默认值为false
zipAlignEnabled是否开启开启zipalign优化,提高apk运行效率
proguardFileProGuard混淆所使用的ProGuard配置文件
proguardFiles同事配置多个ProGuard配置文件
signingConfig配置默认的签名信息
multiDexEnabled是否启用自动拆分多个Dex的功能

4.2.3 signingConfigs块

用于配置签名设置,一般用来配置release模式。

属性描述
storeFile签名证书文件
storePassword签名证书文件的密码
storeType签名证书的类型
keyAlias签名证书中密钥别名
keyPassword签名证书中密钥的密码

gradle

signingConfigs {
        release {
            storeFile file('C:/Users/liuwangshu/.android/release.keystore')
            storePassword 'android'
            keyAlias 'androidreleasekey'
            keyPassword 'android'
           
        }

4.2.4 其他配置块

android块中除了前面讲的defaultConfig块、buildTypes块、signingConfigs块还有其他的配置块,这里列举一些。

描述
sourceSets配置目录指向
productFlavors多个渠道配置
lintOptionsLint配置
dexOptionsDEX工具配置
adbOptionsadb配置
packagingOptions打包时的相关配置

更多的配置块请参考官方文档

4.2.5 全局配置

如果有多个module的配置是一样的,可以将这些配置提取出来,也就是使用全局配置。全局配置有多种方式,这里介绍其中的两种。
1. 使用ext块配置
在项目build.gradle中使用ext块,如下所示。

gradle

ext{
    compileSdkVersion =28
    buildToolsVersion ="28.0.3"
    minSdkVersion =15
    targetSdkVersion =28
}

在某个module的build.gradle中使用配置:

gradle

apply plugin: 'com.android.application'
android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    defaultConfig {
        applicationId "com.example.liuwangshu.hookinstrumentation"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
  ...
}
...

2. 使用config.gradle配置
首先在根目录下创建config.gradle文件来进行配置。
config.gradle

gradle

ext{
    android=[
            applicationId:"com.example.liuwangshu.hookinstrumentation",
            compileSdkVersion :28,
            buildToolsVersion :"28.0.3",
            minSdkVersion : 15,
            targetSdkVersion : 28,
    ]

    dependencies =[
            "appcompat-v7" : "com.android.support:appcompat-v7:28.0.0",
            "constraint"  : "com.android.support.constraint:constraint-layout:1.1.3",
    ]
}

接着在项目build.gradle中添加apply from: "config.gradle",这样项目的所有module都能用config.gradle中定义的参数。
最后在module的build.gradle中使用配置:

gradle

apply plugin: 'com.android.application'
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion
    defaultConfig {
        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
   ...
dependencies {
    implementation rootProject.ext.dependencies["constraint"]
    implementation rootProject.ext.dependencies["appcompat-v7"]
    ...
}

4.3 dependencies 块

dependencies 块用于配置该module构建过程中所依赖的所有库。Gradle插件3.4版本新增了 api 和 implementation 来代替 compile 配置依赖,其中 api 和此前的 compile是一样的。dependencies和api主要以下的区别:

  • implementation可以让module在编译时隐藏自己使用的依赖,但是在运行时这个依赖对所有模块是可见的。而api与compile一样,无法隐藏自己使用的依赖。
  • 如果使用api,一个module发生变化,这条依赖链上所有的module都需要重新编译,而使用implemention,只有直接依赖这个module需要重新编译。

 

1.Android签名文件配置

在一般公司中,当团队比较小的时候,App的签名信息都是放到项目中的,甚至会上传到github上,这样做很是方便。但随着团队人数的增多,这样做的风险会越来越大,因为签名信息是重要的资源,这样就不能将签名上传到github上,也就不应该在build.gradle中直接配置签名。
主要有以下的几种解决方法:
1.自定义一个签名配置文件
2.本地~/.gradle/gradle.properties文件中配置签名信息

1.1 自定义签名信息文件

首先,在工程的目录下新建一个文件夹,内部存储签名文件和签名信息文件。签名文件为gradledemo.jks,签名信息文件为keystore.properties。keystore.properties中的配置如下所示。

Code

STORE_FILE=../signfiles/gradledemo.jks
KEY_ALIAS=gradle
STORE_PASSWORD=jinjiesanbuqu
KEY_PASSWORD=jinjiesanbuqu

当然不要忘了在.gitignore中将gradledemo.jks和keystore.properties忽略掉。接着在模块build.gradle中进行配置,如果还不清楚什么是模块build.gradle和项目build.gradle,看Android Gradle (一)Gradle的Android插件入门这篇文章。
在模块build.gradle中加入如下代码。

java

apply plugin: 'com.android.application'
android {
 ...
}
def setSigningProperties(){
    def propFile = file('../signfiles/keystore.properties')
    if (propFile.canRead()){
        def Properties props = new Properties()
        props.load(new FileInputStream(propFile))
        if (props!=null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
            android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
            android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
            android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
            android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
        } else {
            throw new Exception("some key missing")
        }
    }else {
        throw new Exception("keystore.properties not found:" + propFile.absolutePath)

    }
}

setSigningProperties方法用于读取keystore.properties文件中的签名文件的信息。
最后在模块build.gradle中的signingconfigs块中调用setSigningProperties方法就可以了。

java

apply plugin: 'com.android.application'

android {
 ...
    signingConfigs {
        release {
            setSigningProperties()
        }
    }
}

1.2 本地添加签名信息文件

还可以将签名文件和签名信息文件放到本地中。比如签名文件放到/.gradle/gradledemo.jks,签名信息文件放到/.gradle/keystore.properties。这样签名文件和签名信息文件都不会提交到github上。
keystore.properties的内容如下。

Code

GRADLEDOME_RELEASE_STORE_FILE=~/.gradle/release-key.keystore
GRADLEDOM_RELEASE_KEY_ALIAS=key-alias
GRADLEDOM_RELEASE_STORE_PASSWORD=pass
GRADLEDOM_RELEASE_KEY_PASSWORD=pass

在模块build.gradle中的signingconfigs块中配置签名,如下所示。

java

signingConfigs {
        release {
            storeFile file(GRADLEDOME_RELEASE_STORE_FILE)
            storePassword GRADLEDOME_RELEASE_STORE_PASSWORD
            keyAlias GRADLEDOME_RELEASE_KEY_ALIAS
            keyPassword GRADLEDOME_RELEASE_KEY_PASSWORD
        }
    }

除了这两点,还可以将签名文件和签名信息文件放在专门打包的服务器上,在打包的时候读取即可。这个涉及的内容就多了,就不在本文进行说明了。

2.Gradle的库依赖

现在一个Android项目都是需要去引入其他的库,比如jar、aar、Module等等,现在我们分别来介绍下。下面例子的代码如果不特意说明均是写在模块build.gradle中的。
Gradle的本地库依赖
关于jar依赖可以按照如下这么写,可以指定一个也可以指定多个jar。

java

//依赖引入libs下所有的jar
implementation fileTree(dir:'libs',include:['*.jar'])

//指定依赖某一个或几个jar
implementation files('libs/XXX.jar','libs/XXX.jar')

aar依赖需要额外增加一些语句,如下所示。

java

android {
    ...
    repositories { 
        flatDir {
            dirs "libs"
        }
    }
}    
dependencies {
implementation fileTree(dir:'libs',include:['*.aar'])
implementation(name:'XXX',ext:'aar')
}

Gradle的本地Module依赖
当项目中有多个Module时,我们需要在settings.gradle中引入,如下所示。

java

include ':app'
include ':library1', ':library2'

接着在模块build.gradle引入。

java

implementation project(':library1')

Gradle的远程库依赖
当在Android Studio中新建一个项目时,会在项目build.gradle有如下代码:

java

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.0'
    }
}

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

这些代码都是默认的,在buildscript和allprojects块中,通过repositories来引入谷歌的Maven库和JCenter库。首先会从谷歌的Maven库去寻找相应的库,如果找不到会从JCenter库中去寻找。
然后在模块build.gradle加入如下的代码,就可以引入远程库。

java

implementation group:'com.android.support',name:'appcompat-v7',version:'28.0.0'
//简写
implementation 'com.android.support:appcompat-v7:28.0.0'

3.Gradle的库依赖管理

随着Gradle依赖的库越来越多,那么必然会产生一些问题,比如依赖冲突的问题,为了解决依赖冲突,我们需要先了解Gradle的库依赖管理的几个技术点。

3.1 Gradle的依赖传递

Gradle默认是支持依赖传递的,所以当用到Gradle依赖时一定会涉及到它,是必须要知道的一个知识点。
那什么是依赖传递呢?举一个最简单的例子。
projectC依赖projectB,projectB依赖projectA,那么projectC就依赖了projectA。
依赖传递会产生一些问题,比如重复依赖、依赖错误等问题,那么我们可以通过transitive来禁止依赖传递。

java

implementation('com.xxx.xxx:xxx:3.6.3') {
     transitive false
 }

上面禁止了com.xxx.xxx:xxx:3.6.3库的依赖传递,还可以使用如下语句来关闭当前模块的所有库的依赖传递:

java

configurations.all {
   transitive = false
}

只不过这样就需要手动添加当前模块的每个库的依赖项,一般不会这么做。

3.2 Gradle的依赖检查

有了依赖检查,我们可以解决依赖产生的问题。依赖检查有很多种方式,分别来介绍下。

使用Gradle的命令行
可以直接使用Gradle的命令行来进行依赖检查,拿Windows平台来说,使用cmd进入项目的根目录,执行gradle :app:dependencies即可,其中app是我们新建工程时默认的模块的名称。日志输出很多,下面截取一部分:

Code

+--- com.android.support:appcompat-v7:28.0.0
|    +--- com.android.support:support-annotations:28.0.0 //1
|    +--- com.android.support:support-compat:28.0.0 //2
|    |    +--- com.android.support:support-annotations:28.0.0
|    |    +--- com.android.support:collections:28.0.0
|    |    |    \--- com.android.support:support-annotations:28.0.0
|    |    +--- android.arch.lifecycle:runtime:1.1.1
|    |    |    +--- android.arch.lifecycle:common:1.1.1
|    |    |    |    \--- com.android.support:support-annotations:26.1.0 -> 28.0.0 //3
|    |    |    +--- android.arch.core:common:1.1.1
|    |    |    |    \--- com.android.support:support-annotations:26.1.0 -> 28.0.0
|    |    |    \--- com.android.support:support-annotations:26.1.0 -> 28.0.0
|    |    \--- com.android.support:versionedparcelable:28.0.0
|    |         +--- com.android.support:support-annotations:28.0.0
|    |         \--- com.android.support:collections:28.0.0 (*)

上面是appcompat-v7:28.0.0库的依赖树的一小部分,appcompat-v7:28.0.0依赖了注释1处和注释2的库,注释2处的库又依赖了com.android.support:support-annotations:28.0.0和com.android.support:collections:28.0.0,因此当我们引入appcompat-v7:28.0.0时,会自动下载所有它依赖传递的库。注释3处说明,Gradle在依赖传递时,会自动提升依赖传递的库的版本,默认使用最高版本的库。

使用Gradle面板
除了命令行,还可以使用Android Studio中的右侧的Gradle面板,找到app模块,展开后找到help目录中的dependencies,如下图所示。
eujYaq.png
双击或者右键选择第一个选项即可执行命令,日志就会在AS中Run窗口中打印出来。
现在再举个例子,拿我们熟悉的retrofit举例,在模块build.gradle中引入retrofit:

java

implementation 'com.squareup.retrofit2:retrofit:2.6.0'

执行依赖检查命令,打印的关于retrofit的日志如下:

Code

+--- com.squareup.retrofit2:retrofit:2.6.0
|    \--- com.squareup.okhttp3:okhttp:3.12.0
|         \--- com.squareup.okio:okio:1.15.0

可以很清楚看到retrofit:2.6.0依赖okhttp:3.12.0,而okhttp:3.12.0依赖okio:1.15.0。
这时我们使用3.1小节的transitive试试,修改build.gradle:

java

implementation ('com.squareup.retrofit2:retrofit:2.6.0') {
      transitive false
}

执行依赖检查命令,打印的关于retrofit的日志如下:

Code

+--- com.squareup.retrofit2:retrofit:2.6.0

使用Gradle View插件
如果你觉得前两种方式查看不方便、不直观,还可以使用Android Studio的Gradle View插件。
在AS中选择File–>Settings–>Plugins中搜索gradle view,找到Gradle View插件安装并重启AS,如下图所示。
eujUiV.png

接下来选择View-–>Tools Windows–Gradle View,这时就可以在AS的底部发现Gradle View窗口,里面会显示当前项目的所有依赖树,如下图所示。
eujtI0.png

3.3 Gradle的依赖冲突

依赖冲突产生的原因多是库的版本问题,举个例子,如果在build.gradle中这么写:

java

implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.okio:okio:1.14.0'

在3.2小节中,我们知道retrofit:2.6.0依赖的okio的版本是1.15.0,而这里引入的okio的版本为1.14.0,引入的版本不同就会产生依赖冲突。依赖冲突的解决的关键有两点,一个是Gradle的依赖检查,这个在3.2小节已经讲过了,另一个是利用Gradle的关键字,合理利用它们是解决依赖冲突的关键,在3.1小节已经介绍了 transitive,现在介绍其余的。

3.3.1 force

有时候我们不是想要排除某个库,而是需要强制使用统一的库的版本,force可以强制设置模块的库的版本,在模块build.gradle中加入如下代码。

java

configurations.all {
    resolutionStrategy {
        force 'com.squareup.okio:okio:2.1.0'
    }
}
dependencies {
...
}

强制当前模块的okio的版本为2.1.0,使用依赖检查来查看下retrofit的依赖:

Code

+--- com.squareup.retrofit2:retrofit:2.6.0
|    \--- com.squareup.okhttp3:okhttp:3.12.0
|         \--- com.squareup.okio:okio:1.15.0 -> 2.1.0
\--- com.squareup.okio:okio:1.14.0 -> 2.1.0

可以看到okio的版本都被强制升级到了2.1.0,这样就可以解决一些依赖冲突的问题。

3.3.2 exclude

有些时候需要排除库依赖传递中涉及的库,此时不能靠关闭依赖传递来解决问题,这时可以使用exclude。
我们知道com.android.support:appcompat-v7:28.0.0依赖于com.android.support:support-annotations:28.0.0、com.android.support:support-compat:28.0.0、com.android.support:cursoradapter:28.0.0等库,这时我们不想再依赖support-annotations库,可以这么写。

java

configurations {
    all*.exclude group: 'com.android.support', module: 'support-annotations'
}
dependencies {
...
}

使用依赖检查来查看com.android.support:appcompat-v7:28.0.0的依赖:

Code

+--- com.android.support:appcompat-v7:28.0.0
|    +--- com.android.support:support-compat:28.0.0
|    |    +--- com.android.support:collections:28.0.0
|    |    +--- android.arch.lifecycle:runtime:1.1.1
|    |    |    +--- android.arch.lifecycle:common:1.1.1
|    |    |    \--- android.arch.core:common:1.1.1
|    |    \--- com.android.support:versionedparcelable:28.0.0
|    |         \--- com.android.support:collections:28.0.0
|    +--- com.android.support:collections:28.0.0
|    +--- com.android.support:cursoradapter:28.0.0

和3.2节的日志对比下,可以发现com.android.support:appcompat-v7:28.0.0不再依赖com.android.support:support-annotations:28.0,目的达到了。


版权声明:本文为xfb1989原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。