大型移动应用解决之道 - 依赖管理

读者如果阅读过”插件化”与”组件化”这两篇文章的话,可能多少对下面这张图应该会有印象

image

上图我用红色着重标记出来的Maven仓库,它的作用是什么?为什么会引入这样一台服务器?

如果我们足够细化架构,那么必然会有通用的组件或模块被提取出来,通常每个通用组件或模块都有专门的团队来负责开发维护,既然是通用的,那么其他功能模块的研发团队都需要依赖他们来做事情,而依赖的方式大概有以下两种:

1. 代码依赖

image

就像上图,组件团队提交代码与依赖库到SCM,模块团队将组件工程从SCM上checkout下来,将代码copy到工程内或将组件工程作为lib依赖,这两种做法非常容易引起依赖冲突与无用的依赖,而且这种依赖是直接的代码依赖,具有很大的安全性性问题。

2. 二进制包依赖

二进制包的依赖应该说是比较好的依赖方式(没有安全性等问题),但是面临的问题是以什么样的方式去获取依赖包?

2.1 copy依赖包

组件团队将组件打包成AAR/JAR后,将组件包与依赖的第三方包都上传到约定的服务器上,各模块团队都到指定服务器上去取。 这种方式虽然避免了模块团队直接依赖代码,但是由于需要手动从服务器下载组件包与依赖包,出错概率很高,很有可能出现在手动copy过程中出现依赖版本copy错误的问题,而且要想更新组件所依赖的第三方库的版本也是非常繁琐的事情。

2.2 通过maven

image

在看上图,组件团队将组件打包成AAR/JAR(同时生成POM文件,文件中声明依赖),并通过Maven插件上传到指定的私有Maven服务器上,各模块团队通过Maven插件去引用组件(没有手动过程),而依靠Maven的依赖传递性将自动把各组件所依赖的所有第三方库及第三方库依赖的包进行下载并引入。

很明显2.2部分,使用maven进行依赖的管理是最佳的方案,那么Maven是什么?

Maven是一个项目的管理工具,它能够贯穿整个研发流程,从开发,测试,打包,发布等都能派上用场,而Maven最突出的能力就是依赖管理,也就是我们今天所说的内容。 使用了Maven的依赖管理,从此告别手动copy依赖包,人工维护依赖包版本,依赖冲突与重复依赖等问题,节省了很多时间,同时避免了很多错误。
在Android中,我们基本上都是通过Gradle来操作maven(Gradle继承了maven的依赖管理操作,并将依赖的引用变的更加简单),使用nexus来搭建Maven服务器(nexus实现了maven的依赖管理)。
在iOS中,则通过cocoapods来进行依赖管理,我们后面会在iOS系列文章中讲解cocoapods的使用。

Maven是如何进行依赖管理的,在Gradle中如何使用?

定位依赖包

我们要使用依赖包,首先需要定位到这个依赖包,而在maven中,要定义一个依赖包,需要了解包的定位坐标groupid,artifactId,version,基于三者的信息就可以在本地或远程服务器进行定位。

例如:commons-collections的定位坐标,看下图XML部分定义

image

Gradle中定义一个依赖包

commons-collections:commons-collections:3.2.2

依赖版本

通常我们可能会有多个组件引用了commons-collections包,如果需要统一升级commons-collections版本,我们只能逐个到项目中去修改,这样就增加了出错的概率。 而maven中支持我们通过引用变量的方式,在POM文件中以${变量名}来进行引用。那么我们通过修改这个变量名对应的值就可以做到批量修改对应的版本号。

变量名=值(commonscollections.version = 3.2.2)
Maven配置

<dependency>
  <groupId>commons-collections</groupId>
  <artifactId>commons-collections</artifactId>
  <version>${commonscollections.version}</version>
  </dependency>

Gradle引入

commons-collections:commons-collections:${commonsnet.version}

在Gradle中,我们通常将版本配置在.gradle中,或配置在.properties文件中。

依赖范围

根据依赖范围的配置,我们可以控制所依赖的包,是否被打包进我们的产品中。

<dependency>
  <groupId>commons-collections</groupId>
  <artifactId>commons-collections</artifactId>
  <version>${commonscollections.version}</version>
  <scope>provided</scope> 
</dependency>
依赖范围      是否被打包
compile        true
runtime        true
provided       false
test           false

依赖分类

通常可以通过groupid(组ID),artifactId(依赖包名称),version(依赖包版本)来定位一个依赖包。 但是有时,我们还需要依赖包的其他部分:比如,源码(src),资源(sources),文档(javadoc)等,这些包他们的groupid,artifactid,version是相同的,只是类型不同,也就是classifier不同。 我们看下dbutils组件都做了哪些分类,从下图XML部分,可以看到当前classifier是src,也就表明,这是一个源代码的包。

image

从下图XML中,可以看到当前classifier是sources,表明这是一个资源的包。

image

我们也可以将同一个jar包打包成两个不同classifier的jar包来进行区分,使用者根据classifier来进行引用。

通过classifier,使我们可供依赖的资源变的更加丰富和灵活。

通过Gradle来引入classifier的依赖包

Gradle引入

commons-dbutils:commons-dbutils:1.6:sources

依赖传递

假设我们有模块A , B, C , D, A->B(A直接依赖B),B->C,C->D。那么A间接依赖B,C,D模块(A->B->C->D),这就是依赖传递。

禁止传递

Maven配置,排除所有依赖

<dependency>  
    <groupId>xx.xx</groupId>  
    <artifactId>YY</artifactId>  
    <version>oo</version>  
    <exclusions>  
        <exclusion>  
            <groupId>*</groupId>  
            <artifactId>*</artifactId>  
        </exclusion>  
    </exclusions>  
</dependency>  

gradle禁止依赖传递

compile('xx:YY:xx') {
    transitive = false
}

依赖排除

按照上面例子,A间接依赖B,C,D模块,如果A想排除对D的依赖,可以在pom文件中添加如下配置。

<dependency> 
  <groupId>xx.xx</groupId> 
  <artifactId>YY</artifactId> 
  <version>oo</version>
  <exclusions> 
    <exclusion> 
      <groupId>xx.xx</groupId> 
      <artifactId>YY</artifactId>
     </exclusion>
  </exclusions> 
</dependency>

在gradle中,排除依赖

dependencies {
    compile("xx.xx.xx:YY:oo") {
        exclude group: 'xx.xx', module: 'YY'
    }
}

依赖冲突

A->B->collections3.1, B->C->D->collections3.2.2

从上面依赖关系可以看出,B依赖collections3.1,而按照B的依赖路径,同时也会依赖collections3.2.2,细心的读者可能发现了问题。

maven会选择最短的路径进行依赖,也就是v3.1,而我们本意是要使用最新的版本做为依赖,这样可能会导致崩溃或功能性错误。我们可以缩短高版本的依赖路径,修改POM文件为A->B->collections3.2.2,而B->C->D->collections3.1
而Gradle默认是按照高版本进行依赖的, 即便上面的依赖路径,Gradle也会去依赖v3.2.2的版本。而有时,我们可能也需要强制的依赖某个版本,就像下面:

compile('xxx.xxx:YY:oo') {
    force = true
}

全局配置强制依赖

configurations.all {
    resolutionStrategy {
      force 'xx.xx:YY:oo'
    }
 }

通过上面的讲述,相信读者基本已经了解maven的依赖管理的优势。 那么如何搭建自己的Maven服务器呢? 上面也提到了使用Nexus,关于Nexus的搭建教程推荐大家这篇文章

http://blog.csdn.net/wang379275614/article/details/43940259

通过这篇文章,相信读者也基本了解了Nexus相关概念和搭建技巧。

而如何去操作nexus,通常有两种方法:
1. 手动上传
在nexus的web ui上进行管理

2. 通过gradle的maven-plugin
通过gradle命令进行管理

gradle如何配置和使用命令

1. 配置
Nexus相关配置 build-nexus-config.gradle

gradle.ext {
  NEXUS_SERVER_GROUP='http://ip:port/nexus/content/groups/public/'
  NEXUS_SERVER_RELEASE='http://ip:port/nexus/content/repositories/release/'
  NEXUS_SERVER_SNAPSHOT='http://ip:port/nexus/content/repositories/snapshot/'
  NEXUS_SERVER_UNAME='admin'
  NEXUS_SERVER_UPWD='123456'
}

版本相关配置build-artifact-config.gradle

gradle.ext {
  ARTIFACT_SNAPSHOT_VERSION='1.0.0.5-SNAPSHOT'
  ARTIFACT_RELEASE_VERSION='1.0.0.5'
  ARTIFACT_ID=’common’
  ARTIFACT_GROUP_ID='com.xx.xx'
}

在build.gradle中配置仓库地址

allprojects {
    repositories {
        maven{ url gradle.NEXUS_SERVER_GROUP }
        jcenter()
    }
}

2. 上传组件
在上传时需要区分当前要上传的是snapshot仓库,还是relaese仓库。
可以通过参数来进行区分(由自己定义),默认情况下,发布的为快照版本,如果添加参数名为'repositoryRelease'则表示为正式版本。

Gradle发布snapshot命令:

gradle uploadArchives

Gradle发布release命令:

gradle uploadArchives -P repositoryRelease
apply plugin: 'maven'
apply plugin: "maven-publish"

def isSnapshot = true
if(project.hasProperty('repositoryRelease')){
    isSnapshot = false
}

gradle.ext.isSnapshotVersion = isSnapshot;

uploadArchives {
    repositories {
        mavenDeployer {

            repository(url: isSnapshot ? gradle.NEXUS_SERVER_SNAPSHOT : gradle.NEXUS_SERVER_RELEASE) {
                authentication(userName: gradle.NEXUS_SERVER_UNAME, password: gradle.NEXUS_SERVER_UPWD)
            }

            pom.version = isSnapshot ? gradle.ARTIFACT_SNAPSHOT_VERSION :gradle.ARTIFACT_RELEASE_VERSION
            pom.artifactId = gradle.ARTIFACT_ID
            pom.groupId = gradle.ARTIFACT_GROUP_ID
            
            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')
            
                configurations.compile.allDependencies.each {
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', it.group)
                    dependencyNode.appendNode('artifactId', it.name)
                    dependencyNode.appendNode('version', it.version)
                }
            }
        }
    }
}

通过以上,我们可以总结出下图:

image

从上图可以看出,所有通用组件(业务/技术/SDK等)全部都存储在Nexus服务器上,Nexus上由不同的仓库(snapshot/release/proxy/group等)组成。组件团队与业务团队均通过配置Maven插件达到对指定的仓库进行上传和下载。

另外一个Maven重要的功能,既是代理远程的仓库,见下图:

image

通过代理远端仓库,达到加快本地快速访问的目的。

写到这里,不知道读者是不是已经对依赖管理有了一些了解。如果读者在搭建过程中有任何问题,我们可以一起讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容