【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项目视图如下所示。
所有构建文件在 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的包名 |
minSdkVersion | App最低支持的SDK版本 |
targetSdkVersion | 基于哪个SDK版本开发 |
versionCode | App内部的版本号,用于控制App升级 |
versionName | App版本名称,也就是发布的版本号 |
testApplicationId | 配置测试App的包名 |
testInstrumentationRunner | 配置单元测试使用的Runner,默认为android.test.InstrumentationTestRunner |
proguardFile | ProGuard混淆所使用的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运行效率 |
proguardFile | ProGuard混淆所使用的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 | 多个渠道配置 |
lintOptions | Lint配置 |
dexOptions | DEX工具配置 |
adbOptions | adb配置 |
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,如下图所示。
双击或者右键选择第一个选项即可执行命令,日志就会在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,如下图所示。
接下来选择View-–>Tools Windows–Gradle View,这时就可以在AS的底部发现Gradle View窗口,里面会显示当前项目的所有依赖树,如下图所示。
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,目的达到了。