tags: [android,gradle,CI/CD]
categories: gradle
top: false
toc: true
cover: false
img: /featureImg/gradle.png
summary: Android项目结构和依赖管理
—-

项目结构

gradle主要有两种项目结构,水平结构和分层结构

分层结构

image.png

分层结构是一个根项目下包含了一个或者多个子项目(AndroidStudio中的module)
在setting.gradle中使用 include 'ModuleName' 引入分层结构的子项目

水平结构

image.png
水平结构是一个根项目,它的子项目是和根项目同级的
在setting.gradle中使用 includeFlat 'ModuleName' 引入平行结构的子项目

Android的项目结构

AndroidStudio创建的默认工程是一个分层结构的项目,而根目录只用来配置子项目(setting.gradle)和进行子项目统一配置(build.gradle),没有源码和资源等

而在某些特殊的场景,根项目是可以作为一个独立的module来处理
之前公司使用了git-repo 将多个module分开到多个git仓库中进行管理,通过将setting.gradle和build.gradle作为公共模块也用一个git仓库进行管理,repo到本地后再复制出来的方式保持了项目的分层结构(个人认为这里应该采用水平结构)
后来因为CI集成的原因又需要将库打成aar包并且只保留app一个module,这种场景下将app module直接作为根项目处理可以保留之前的git提交记录,减少迁移的成本
改动也比较简单,在app module下加入一个空的setting.gradle(setting.gradle不是必须的,没有的情况下会作为一个单一的project处理),将之前根目录下build.gradle的内容复制到app module的build.gradle中,这样就可以直接将app module所在的文件夹导入AndroidStudio作为一个完整的project来使用

配置方式

gradle一大功能就是进行项目配置,有两种配置方式

集中配置

在根目录的build.gradle中将所有的配置全部搞定

  1. buildscript {
  2. ext.kotlin_version = '1.3.41'
  3. repositories {
  4. google()
  5. jcenter()
  6. }
  7. dependencies {
  8. classpath 'com.android.tools.build:gradle:3.4.2'
  9. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
  10. // NOTE: Do not place your application dependencies here; they belong
  11. // in the individual module build.gradle files
  12. }
  13. }
  14. //所有项目配置
  15. allprojects {
  16. repositories {
  17. google()
  18. jcenter()
  19. }
  20. }
  21. //app module配置
  22. project('app') {
  23. apply plugin: 'com.android.application'
  24. apply plugin: 'kotlin-android'
  25. apply plugin: 'kotlin-android-extensions'
  26. android {
  27. compileSdkVersion 28
  28. buildToolsVersion "29.0.2"
  29. defaultConfig {
  30. applicationId "me.sunhapper.myapplication"
  31. minSdkVersion 16
  32. targetSdkVersion 28
  33. versionCode 1
  34. versionName "1.0"
  35. testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  36. }
  37. buildTypes {
  38. release {
  39. minifyEnabled false
  40. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  41. }
  42. }
  43. }
  44. dependencies {
  45. implementation fileTree(dir: 'libs', include: ['*.jar'])
  46. implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
  47. implementation 'com.android.support:appcompat-v7:28.0.0'
  48. implementation 'com.android.support.constraint:constraint-layout:1.1.3'
  49. implementation 'com.android.support:design:28.0.0'
  50. testImplementation 'junit:junit:4.12'
  51. androidTestImplementation 'com.android.support.test:runner:1.0.2'
  52. androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
  53. }
  54. }
  55. task clean(type: Delete) {
  56. delete rootProject.buildDir
  57. }

这种情况下app module下不需要build.gradle,但是这是极端情况,非常不便于维护,单个的build.gradle会变得非常巨大

分散配置

实际项目中,根目录下一般只处理通用的配置,而子项目的配置放到相对应的build.gradle中维护

依赖管理

可以使用 ./gradlew moduleName:dependencies 查看项目的依赖信息
image.png

gradle官方提供了一个project-report的gradle插件 apply plugin: 'project-report' ,创建了下面五个task

任务名称 描述
dependencyReport 将项目依赖情况输出到txt文件中 功能同gradle dependencies > build/dependenciestxt
htmlDependencyReport 生成HTML版本的依赖情况
propertyReport 生成项目属性报告
taskReport 生成项目任务报告
projectReport 生成项目报告,包括前四个

./gradlew :dependencyInsight --configuration $configuration --dependency $dependency 可以反向查找哪些库依赖了这个依赖
image.png

传递依赖

当某个库依赖了其他的库,gradle会把这些依赖都引入进来,这样可以省的我们一个个引入每一个依赖
但是这也会造成依赖冲突

依赖冲突

  • 模块 A → Lib B(2.0.3) → Lib C(2.0.3)
  • 模块 A → Lib D(2.7.0-SNAPSHOT) → Lib E(2.7.0-SNAPSHOT) → Lib C(2.7.0-SNAPSHOT)

这里的Lib C 就出现了依赖冲突

gradle本身有一套机制来处理依赖冲突,正常情况下会使用最新版本
之前查看依赖的图中
-> 表示最终使用的是这个后面版本的依赖
(*) 表示这个传递依赖有其他版本或者相同版本的库存在,传递依赖不会被引入

但是有些时候使用最新版本并不一定符合我们的要求,比如android support包需要和compileSdkVersion的版本对应,假如有依赖引入了更新的support库,那就会打包失败了

为了解决这类问题,我们有几种方式

exclude

exclude应该是最常用的解决这类依赖冲突的方式,它可以剔除传递依赖

  1. compile("xxx") {
  2. //可以直接按group剔除,也可以精确到module
  3. exclude group: 'com.android.support',module:'appcompat-v7'
  4. }

transitive

设置transitive=false 可以取消依赖传递

  1. //全局取消依赖传递
  2. configurations.all {
  3. transitive = false
  4. }
  5. //取消某个依赖的传递性
  6. compile('xxx') {
  7. transitive = false
  8. }

在依赖后指定依赖产物如@jar/@aar也可以取消依赖传递,相当于依赖了一个本地的文件
使用这种方式解决依赖冲突可能造成某个传递依赖未引入需要自己手动引入

force

强制设置某个依赖的版本

  1. configurations.all {
  2. resolutionStrategy {
  3. force 'org.hamcrest:hamcrest-core:1.3'
  4. }
  5. }
  6. compile('org.hibernate:hibernate:3.1') {
  7. force = true
  8. }

dependency

管理依赖的依赖

  1. compile module("xxx") {
  2. //这里指定的依赖可能会被其他传递依赖覆盖,不具有强制性
  3. //要达到目的需要所有包含要指定版本传递依赖的依赖都指定dependency
  4. //不如使用force
  5. dependency("com.android.support:support-annotations:25.3.1")
  6. }

冲突即停

发现依赖冲突就会构建失败

  1. configurations.all {
  2. resolutionStrategy {
  3. failOnVersionConflict()
  4. }
  5. }

依赖更新

gradle可以将依赖缓存在本地,减少每次下载依赖耽误的时间
如果你是一个上层的业务开发,可能并不太需要关注gradle的缓存,依赖只要下载下来用就可以了
但是如果你是一个底层库的开发者,要保证测试的是你刚发布到maven仓库的版本,这就需要关心缓存的更新了

更改版本号

这是一个笨办法,每次升级一下发布的版本号确实可以保证每次测试的都是最新的代码,但是这意味着每次都需要手动的修改版本,非常繁琐

使用缓存更新策略

  1. configurations.all {
  2. //每隔10秒检查远程依赖是否存在更新
  3. resolutionStrategy.cacheChangingModulesFor 10, 'seconds'
  4. // 采用动态版本(使用+声明的版本)声明的依赖缓存10分钟
  5. resolutionStrategy.cacheDynamicVersionsFor 10*60, 'seconds'
  6. }

配合设置依赖的changing属性,可以让gradle即使没有更新版本号都会去仓库更新代码

  1. compile('xxx') {
  2. changing = true
  3. }

暂时先总结这么多,以后想到什么gradle in android的进阶技巧再更新

参考资料

GradleSide
Authoring Multi-Project Builds
Gradle Configuration-依赖