大型移動應用解決之道 - 依賴管理

讀者如果閱讀過”插件化”與”組件化”這兩篇文章的話,可能多少對下面這張圖應該會有印象

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

通過代理遠端倉庫,達到加快本地快速訪問的目的。

寫到這裏,不知道讀者是不是已經對依賴管理有了一些瞭解。如果讀者在搭建過程中有任何問題,我們可以一起討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章