在中大型Android项目中,都会有多个Module进行协同配合。这些module中可能会依赖同一个库的不同版本,这将导致一些问题,要么是代码冲突,要么是APK包体积增大,亦或是项目构建的时间变长,拖慢开发效率。 例如:下图就是在不同的module中依赖同一个库的不同版本。

要解决这个问题我们首先要了解在Android项目中目前有那些方案来引入第三方依赖:
直接编写是Android项目工程自带的默认管理方式,在每一个module中都写死了不同依赖及版本号,因此每次升级依赖库时都需要对每一个module做大量的手动更改。
module_a/build.gradle:
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}
module_b/build.gradle:
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}
如果是单个module的项目非常适合这种比较简单的方式,一目了然。远端依赖如果升级了新的版本会有提供升级的功能。
如果项目中有多个不同的module,你需要在多个module的build.gradle文件中都做对应的配置,还要确保依赖的库的版本号一致,配置项要重复编写多次。
Google在Android官方文档中推荐通过使用gradle的extra属性,将依赖及版本号编写到config.gradle配置文件中,每个module都去依赖config.gradle中的版本,从而达到统一管理的目的。
Root-level config.gradle
ext {
versions = [
core_ktx: "1.7.0",
appcompat: "1.4.1",
okhttp: "4.10.0"
]
libs = [
core_ktx :"androidx.core:core-ktx:${versions.core_ktx}",
appcompat: "androidx.appcompat:appcompat:${versions.appcompat}",
okhttp: "com.squareup.okhttp3:okhttp:${versions.rxjava}"
]
}
Root-level build.gradle
apply from: "config.gradle"
module_a/build.gradle
dependencies {
implementation libs.core_ktx
implementation libs.appcompat
implementation libs.okhttp
}
module_b/build.gradle
dependencies {
implementation libs.core_ktx
implementation libs.appcompat
implementation libs.okhttp
}
将版本统一管理起来了,只需要更改config.gradle中的版本号就可以对所有module的依赖版本进行修改。
Gradle文档中说到:当运行 Gradle 时会检查项目中是否存在一个名为 buildSrc 的目录。 然后Gradle 会自动编译并测试这段代码,并将其放入构建脚本的类路径中, 对于多项目构建,只能有一个 buildSrc 目录,该目录必须位于根项目目录中, buildSrc 是 Gradle 项目根目录下的一个目录, 它可以包含我们的构建逻辑,与脚本插件相比,buildSrc应该是首选,因为它更易于维护、重构和测试代码。
1.项目根目录下新建一个名为buildSrc的文件夹(注意:名字必须是 buildSrc,因为上文提到了运行 Gradle 时会检查项目中是否存在一个名为 buildSrc 的目录),然后在 buildSrc 文件夹里创建名为 build.gradle.kts 的文件。
build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
google()
gradlePluginPortal()
}
buildSrc下新建目录src\main\kotlin这里的目录只是建议,输入其他的名称也是可以的。
在src\main\kotlin中创建Dependencies.kt等配置文件:
Dependencies.kt
object BuildVersion {
const val compileSdk = 30
const val buildTools = "30.0.2"
const val minSdk = 21
const val targetSdk = 30
const val versionCode = 1
const val versionName = "1.0.0"
}
object Versions {
val core_ktx = "1.7.0"
val appcompat = "1.4.1"
val okhttp = "4.10.0"
}
object Libs {
val core_ktx = "androidx.core:core-ktx:${Versions.core_ktx}"
val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
val okhttp = "com.squareup.okhttp3:okhttp:${Versions.okhttp}"
}
4.经过上面步骤后,执行一次Gradle Sync任务,就可以在Android Studio中访问Dependencies.kt中任何值了。 看起来结果与“ext”方式非常相似,但是它支持自动补全和单击跳转功能。
module_a/build.gradle
android {
compileSdkVersion BuildVersion.compileSdk
buildToolsVersion BuildVersion.buildTools
defaultConfig {
minSdkVersion BuildVersion.minSdk
targetSdkVersion BuildVersion.targetSdk
versionCode BuildVersion.versionCode
versionName BuildVersion.versionName
...
}
...
}
dependencies {
implementation Libs.core_ktx
implementation Libs.appcompat
implementation Libs.okhttp
}
module_b/build.gradle
android {
compileSdkVersion BuildVersion.compileSdk
buildToolsVersion BuildVersion.buildTools
defaultConfig {
minSdkVersion BuildVersion.minSdk
targetSdkVersion BuildVersion.targetSdk
versionCode BuildVersion.versionCode
versionName BuildVersion.versionName
...
}
...
}
dependencies {
implementation Libs.core_ktx
implementation Libs.appcompat
implementation Libs.okhttp
}
摘自 Gradle 文档:复合构建只是包含其他构建的构建. 在许多方面,复合构建类似于 Gradle 多项目构建,不同之处在于,它包括完整的 builds,而不是包含单个 projects
新建Android library工程module名为version_plugin,包名为com.huke.plugin(非固定名可以自取)并删除多余的文件及文件夹。

修改version_plugin工程中的 build.gradle文件。
apply plugin: 'kotlin'
apply plugin: 'java-gradle-plugin'
buildscript {
repositories {
google()
maven { url 'https://jitpack.io' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
gradlePluginPortal()
mavenCentral()
}
dependencies {
// 因为使用的 Kotlin 需要需要添加 Kotlin 插件,需要和主工程对应,不然就出现两个版本了
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0"
}
}
repositories {
google()
maven { url 'https://jitpack.io' }
maven {url 'https://maven.aliyun.com/nexus/content/repositories/releases/'}
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
mavenCentral()
}
dependencies {
//添加Gradle相关的API,否则无法自定义Plugin和Task
implementation gradleApi()
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0"
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
gradlePlugin {
plugins {
version {
// 在 app 模块需要通过 id 引用这个插件
id = 'com.huke.plugin'
// 实现这个插件的类的路径
implementationClass = 'com.huke.version.VersionPlugin'
}
}
}
注意,插件中的build.gradle文件中的 kotlin-gradle-plugin 版本要和主工程对应。
includeBuild("version_plugin"),添加完毕以后可以Rebuild项目。includeBuild('version_plugin')
import org.gradle.api.Plugin
import org.gradle.api.Project
class VersionPlugin : Plugin<Project> {
override fun apply(target: Project) {
}
}
5.在plugin-version module中创建Deps.kt文件用来管理依赖包和版本号,可以声明多个kt文件。
/**
* 配置和 build相关的
*/
object BuildVersion {
const val compileSdk = 30
const val buildTools = "30.0.2"
const val minSdk = 21
const val targetSdk = 30
const val versionCode = 1
const val versionName = "1.0.0"
}
object Versions {
val core_ktx = "1.7.0"
val appcompat = "1.4.1"
val okhttp = "4.10.0"
}
object Libs {
val core_ktx = "androidx.core:core-ktx:${Versions.core_ktx}"
val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
val okhttp = "com.squareup.okhttp3:okhttp:${Versions.okhttp}"
}
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.ileo.plugin.version'
//引用插件框架
id 'com.huke.plugin'
}
/导入依赖包下的类
import com.huke.version.*
android {
compileSdk BuildVersion.compileSdk
defaultConfig {
applicationId "com.huke.composingbuildsdemo"
minSdk BuildVersion.minSdk
targetSdk BuildVersion.targetSdk
versionCode BuildVersion.versionCode
versionName BuildVersion.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
//引用依赖
implementation Dependencies.core_ktx
implementation Dependencies.appcompat
implementation Dependencies.okhttp
}
module_b/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.ileo.plugin.version'
//引用插件框架
id 'com.huke.plugin'
}
/导入依赖包下的类
import com.huke.version.*
android {
compileSdk BuildVersion.compileSdk
defaultConfig {
applicationId "com.huke.composingbuildsdemo"
minSdk BuildVersion.minSdk
targetSdk BuildVersion.targetSdk
versionCode BuildVersion.versionCode
versionName BuildVersion.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
//引用依赖
implementation Dependencies.core_ktx
implementation Dependencies.appcompat
implementation Dependencies.okhttp
}
tip 如果import com.huke.version.*无法导入可以先使用implementation添加依赖再进行import导包或者 使用com.huke.version.Dependencies.okhttp进行导入。
直接编写(默认方式)这种方式适合单一module项目使用,并且它并不是一无是处,在远端的依赖版本更新时,它会有提示功能,这是其他几种方式目前无法做到的。使用gradle的extra属性这种方式是google之前推荐的一种方式,不过它并不支持点击跳转,如果要查看依赖的详情必须要进行搜索查看找到对应的源头进行查看。 使用buildSrc管理这种方式比较简单,支持跳转,但是要注意版本号变更以后编译时间比较长。使用Composing builds管理也支持跳转,但是配置稍复杂。目前看来这几种方案似乎都没有做到完美,大家可以根据自己的工程灵活选择其中一种方式使用。
统一依赖管理Composing builds
项目依赖统一管理方案
Android项目管理依赖方式总结
Android - 依赖统一管理
再见吧 buildSrc, 拥抱 Composing builds 提升 Android 编译速度
Composing builds
统一依赖管理Composing builds
我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s