1. 程式人生 > >關於Android中多module使用fat-aar合併的坑

關於Android中多module使用fat-aar合併的坑

原文地址:
https://juejin.im/post/5b28672bf265da59645b031a

概述

簡單介紹一下專案情況,筆者做這個專案快兩年了,之所以有這篇文章,源於專案的需求,因為專案除了公司內部使用,還需要抽取sdk給第三方合作公司使用,並且不同的合作方可能會對sdk作改動,A公司可能不要錄屏功能,B公司可能只要視訊播放功能,不要視訊釋出,如何在不侵入我們主版本業務的情況下解決這個問題呢?

聰明的你肯定想到了,切分支唄!

這種方式只能解決一時之需,但是後續的主版本迭代,差異會越來越大,主版本同步到SDK的效率越來越低。
所以一旦是你採用了此種方案,我個人建議你做好跑路的準備!

所以去年底的時候,將元件化落地到了專案中,將各個模組獨立,按照第三方的需要,實現靈活的搭配,這樣一來,可以解耦我們的各個模組,便於維護,也可以適應第三方的定製需求,但是今天筆者討論的並非元件化相關的內容,與該主題相關的內容很多,筆者今天想討論的是元件化之後踩到的一些坑,,可能這些坑你永遠也不會碰到,但是既然來了,看完又何妨呢?

目前專案架構如圖所示:

元件化已基本完成,這才邁開了第一步,如何實現差異化呢?

1.分別打包

將各個獨立的元件分別打包成對應的aar,提供給第三方,但是又涉及到一個問題,那就是混淆的問題,如果直接分別提供原始的aar包,那麼原始碼幾乎等於完全暴露,如果分別混淆,又會存在一個問題,公共元件中常用的工具類被混淆,上層的短視訊這些元件就會找不到對應的類。

2.合併打包

這種方案具備良好的可行性,因為最終合併的檔案只有一個,便於混淆,遺憾的是Android官方並沒有提供這種合併的操作,但是發現github上有作者開源了一個合併指令碼[fat-aar.gradle](https://github.com/adwiv/android-fat-aar),這個指令碼的作用實際就是合併我們的多個元件為一個aar

合併的坑

下面筆者將用一個示例工程來演示合併的一些相關問題。
合併元件工程示例

用一張簡單的圖來描述其中的依賴關係

最上層的是我們要生成的最終的merge.aar
他會合並直播間liveroom模組,合併video視訊模組,而對應的模組也會依賴下層的元件,如何依賴合併呢?

apply from: "../fat-aar.gradle"
embedded project(':common')
embedded project(':upload')
embedded project(':download')
embedded project(':video')
embedded project(':liveroom')

注意: 需要合併的元件,只需要在最上層的元件中使用embedded關鍵字標記即可,並且下層所依賴的所有元件,都需要標記一次

接下來直接使用命令打包合併

cd merge
gradle clean asR

合併完成之後你以為就結束了嗎?你太年輕了!!!
當你給別人使用的時候,馬上就會發現第一個坑:

出現這個錯誤的原因,經過筆者肉眼的分析(各種google,各種stackoverflow),發現是由gradle 的外掛版本引起的

筆者工作的環境

系統: ubuntu 16.04
gradle外掛版本是2.3.3
gradle的版本是3.5

降級到gradle外掛版本

classpath 'com.android.tools.build:gradle:2.2.3'

此時編譯直接報錯:

筆者用了一個比較笨的方法:
強行指定fat-aar.gradle指令碼中的版本

終於合併完成!!!
但是不明白這兩者合併出來的aar包差異在哪裡,所以我將兩個外掛版本分別合併的aar包截圖觀察了一下

gradle2.3.3版本合併的aar包

gradle2.2.3版本合併的aar包

可以看到後者打包出來的aar檔案,在libs目錄中有一個jar包,這個jar包裡存放的就是相關的R檔案

所以解決上述問題的方案:

1.降級gradle外掛版本到2.2.3版本,並修改對應腳本里的版本號

2.使用gradle外掛版本2.2.3以上,但是需要手動修改fat-aar.gradle外掛的內容,使之合併相關的R檔案的jar包,這個問題大家也可以思考一下?

要知道,gradle的遠端依賴功能實在是太方便了,我們可以很輕易的指定相關的依賴包,但是由於aar檔案的特殊性,我們在元件中包含的一下遠端依賴並不會被實際的合併到aar中去,例如你遠端依賴了okhttp或者glide等相關的庫,合併aar之後,就會出現如下的錯誤:

如何解決這個問題呢?聰明的你一定想到,maven

我們完全可以把這些依賴合併釋出到maven中去,於是筆者嘗試著搭建了nexus私服,具體的搭建不是本文討論的重點。

幸運的是,fat-aar的作者給我們提供了相關的publish.gradle的指令碼,真的不得不說,想什麼來什麼啊,既然有了現成的輪子,我們就直接跑唄!

在最上層的merge模組中新增依賴

apply from: '../publish.gradle'

並新增如下配置

android {

    ...

    libraryVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.aar')) {
                def fileName = getArtifactFileName()
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }

}

def getArtifactFileName() {
    return "${POM_ARTIFACT_ID}-${VERSION_NAME}.aar"
}

接下來配置自己的nexus私服即可

maven {
     //替換自己搭建的私服
     url "http://127.0.0.1:8081/nexus/content/repositories/releases"
 }

通剛才的合併打包方式,最後釋出到自己的nexus私服上

你以為這樣就結束了嗎?並沒有!!!

實際操作過程中,筆者發現,我們本地實際是有依賴本地第三方的aar包的,換句話說,並非所有的庫都是遠端依賴,你會發現,原來指令碼居然會將本地依賴的aar檔案,也合併到pom.xml檔案中,繼而釋出到nexus私服上去了,這個時候給別人遠端依賴,就會一直找不到相關的本地庫

如何解決呢?

看來偷懶是不行的了,還是得改指令碼,經過筆者的觀察,發現在生成的pom.xml中可以過濾掉

pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')

                depList.values().each {
                    ResolvedDependency dep ->
                        def hasGroup = dep.moduleGroup != null
                        def hasName = (dep.moduleName != null && !"unspecified".equals(dep.moduleName) && !"".equals(dep.moduleVersion))
                        def hasVersion = (dep.moduleVersion != null && !"".equals(dep.moduleVersion) && !"unspecified".equals(dep.moduleVersion))

                        if (hasGroup && hasName && hasVersion) {
                            def dependencyNode = dependenciesNode.appendNode('dependency')
                            dependencyNode.appendNode('groupId', dep.moduleGroup)
                            dependencyNode.appendNode('artifactId', dep.moduleName)
                            dependencyNode.appendNode('version', dep.moduleVersion)
                        }
                }
            }

即把版本號為空的過濾掉即可。

思考與擴充套件

經過一番折騰,好歹也是合併出來我們想要的東西,但是筆者剛剛也說到了,公司主專案除了自己使用,還是組合成sdk給第三方使用,第三方可能會改下首頁的佈局,顏色,等等。如何在不侵入主業務的情況下,作變更呢?其實很簡單,借鑑Android中多渠道包的生成,同名的資源放在不同的目錄
遺憾的是原生的指令碼並不支援這種姿勢,我在最上層的merge模組中使用同名的資源試圖覆蓋下層的資源,達到替換的目的,並未得逞!!!
沒辦法,還是得改指令碼,改動的思想實際就是在指令碼合併的過程中,優先記錄最上層的資源名稱,當合並下層模組的資原始檔時,直接跳過即可,改過的指令碼在文章的末尾。

混淆配置

關於混淆的配置,只需要在最上層的merge模組中配置即可

注意事項

1.儘量不要使用原本的指令碼檔案,因為原作者已經幾年未更新過,文章末尾有筆者的改動過的指令碼檔案

2.各個元件的清單會合並,不需要在最上層的元件中統一註冊

3.本地依賴的jar包不用擔心,因為指令碼會合併到最終aar庫的lib目錄下

4.本地依賴的aar包,要記得隨著遠端依賴,給第三方一起依賴,即第三方除了依賴我們的遠端依賴,還需要本地依賴我們所使用的aar檔案,這也算是一個缺陷吧

5.第三方依賴的外掛版本最好跟我們合併使用的gradle版本一致

小結

到目前為止,合併多元件的幾個坑基本已經走了一遍了,其實在去年底,筆者在公司的直播專案中已經將元件化落地了,而後在實現多元件的道路上也踩了不少坑,本來這篇文章並沒有打算髮布出來,因為並不是所有人都會碰到這類需求,但是前段時間有個朋友公司問了我相關的問題,看他踩坑了很久,所以還是覺得釋出出來,至少對於看到的人而言,以後碰到類似問題,可以少走些彎路,提高效率。

紙上得來終覺淺,絕知此事要躬行!

示例專案

指令碼地址