原文地址:
https://juejin.im/post/5b28672bf265da59645b031a
概述
簡(jiǎn)單介紹一下項(xiàng)目情況妇蛀,筆者做這個(gè)項(xiàng)目快兩年了蚁趁,之所以有這篇文章雅任,源于項(xiàng)目的需求,因?yàn)轫?xiàng)目除了公司內(nèi)部使用属铁,還需要抽取sdk給第三方合作公司使用粱玲,并且不同的合作方可能會(huì)對(duì)sdk作改動(dòng),A公司可能不要錄屏功能昧旨,B公司可能只要視頻播放功能,不要視頻發(fā)布
拾给,如何在不侵入我們主版本業(yè)務(wù)的情況下解決這個(gè)問(wèn)題呢?
聰明的你肯定想到了兔沃,切分支唄蒋得!
這種方式只能解決一時(shí)之需,但是后續(xù)的主版本迭代乒疏,差異會(huì)越來(lái)越大额衙,主版本同步到SDK的效率越來(lái)越低。
所以一旦是你采用了此種方案怕吴,我個(gè)人建議你做好跑路的準(zhǔn)備窍侧!
所以去年底的時(shí)候,將組件化落地到了項(xiàng)目中转绷,將各個(gè)模塊獨(dú)立伟件,按照第三方的需要,實(shí)現(xiàn)靈活的搭配议经,這樣一來(lái)斧账,可以解耦我們的各個(gè)模塊,便于維護(hù)煞肾,也可以適應(yīng)第三方的定制需求咧织,但是今天筆者討論的并非組件化相關(guān)的內(nèi)容,與該主題相關(guān)的內(nèi)容很多扯旷,筆者今天想討論的是組件化之后踩到的一些坑拯爽,,可能這些坑你永遠(yuǎn)也不會(huì)碰到钧忽,但是既然來(lái)了,看完又何妨呢逼肯?
目前項(xiàng)目架構(gòu)如圖所示:
組件化已基本完成耸黑,這才邁開了第一步,如何實(shí)現(xiàn)差異化呢篮幢?
1.分別打包
將各個(gè)獨(dú)立的組件分別打包成對(duì)應(yīng)的aar大刊,提供給第三方,但是又涉及到一個(gè)問(wèn)題三椿,那就是混淆的問(wèn)題缺菌,如果直接分別提供原始的aar包葫辐,那么源代碼幾乎等于完全暴露,如果分別混淆伴郁,又會(huì)存在一個(gè)問(wèn)題耿战,公共組件中常用的工具類被混淆,上層的短視頻這些組件就會(huì)找不到對(duì)應(yīng)的類焊傅。
2.合并打包
這種方案具備良好的可行性剂陡,因?yàn)樽罱K合并的文件只有一個(gè),便于混淆狐胎,遺憾的是Android官方并沒有提供這種合并的操作鸭栖,但是發(fā)現(xiàn)github上有作者開源了一個(gè)合并腳本[fat-aar.gradle](https://github.com/adwiv/android-fat-aar),這個(gè)腳本的作用實(shí)際就是合并我們的多個(gè)組件為一個(gè)aar
合并的坑
下面筆者將用一個(gè)示例工程來(lái)演示合并的一些相關(guān)問(wèn)題握巢。
合并組件工程示例
用一張簡(jiǎn)單的圖來(lái)描述其中的依賴關(guān)系
最上層的是我們要生成的最終的merge.aar
他會(huì)合并直播間liveroom模塊晕鹊,合并video視頻模塊,而對(duì)應(yīng)的模塊也會(huì)依賴下層的組件暴浦,如何依賴合并呢溅话?
apply from: "../fat-aar.gradle"
embedded project(':common')
embedded project(':upload')
embedded project(':download')
embedded project(':video')
embedded project(':liveroom')
注意: 需要合并的組件,只需要在最上層的組件中使用
embedded
關(guān)鍵字標(biāo)記即可肉渴,并且下層所依賴的所有組件公荧,都需要標(biāo)記一次
接下來(lái)直接使用命令打包合并
cd merge
gradle clean asR
合并完成之后你以為就結(jié)束了嗎?你太年輕了M妗Q!
當(dāng)你給別人使用的時(shí)候券勺,馬上就會(huì)發(fā)現(xiàn)第一個(gè)坑:
出現(xiàn)這個(gè)錯(cuò)誤的原因绪钥,經(jīng)過(guò)筆者肉眼的分析(各種google,各種stackoverflow)关炼,發(fā)現(xiàn)是由gradle 的插件版本引起的
筆者工作的環(huán)境
系統(tǒng): ubuntu 16.04
gradle插件版本是2.3.3
gradle的版本是3.5
降級(jí)到gradle插件版本
classpath 'com.android.tools.build:gradle:2.2.3'
此時(shí)編譯直接報(bào)錯(cuò):
筆者用了一個(gè)比較笨的方法:
強(qiáng)行指定fat-aar.gradle腳本中的版本
終于合并完成3谈埂!儒拂!
但是不明白這兩者合并出來(lái)的aar包差異在哪里寸潦,所以我將兩個(gè)插件版本分別合并的aar包截圖觀察了一下
gradle2.3.3版本
合并的aar包
gradle2.2.3版本
合并的aar包可以看到后者打包出來(lái)的aar文件,在libs目錄中有一個(gè)jar包社痛,這個(gè)jar包里存放的就是相關(guān)的R文件
所以解決上述問(wèn)題的方案:
1.降級(jí)gradle插件版本到2.2.3版本见转,并修改對(duì)應(yīng)腳本里的版本號(hào)
2.使用gradle插件版本2.2.3以上,但是需要手動(dòng)修改fat-aar.gradle插件的內(nèi)容蒜哀,使之合并相關(guān)的R文件的jar包斩箫,這個(gè)問(wèn)題大家也可以思考一下?
要知道,gradle的遠(yuǎn)程依賴功能實(shí)在是太方便了乘客,我們可以很輕易的指定相關(guān)的依賴包狐血,但是由于aar文件的特殊性,我們?cè)诮M件中包含的一下遠(yuǎn)程依賴并不會(huì)被實(shí)際的合并到aar中去易核,例如你遠(yuǎn)程依賴了okhttp或者glide等相關(guān)的庫(kù)匈织,合并aar之后,就會(huì)出現(xiàn)如下的錯(cuò)誤:
如何解決這個(gè)問(wèn)題呢耸成?聰明的你一定想到报亩,maven
我們完全可以把這些依賴合并發(fā)布到maven中去,于是筆者嘗試著搭建了nexus私服井氢,具體的搭建不是本文討論的重點(diǎn)弦追。
幸運(yùn)的是,fat-aar的作者給我們提供了相關(guān)的publish.gradle的腳本花竞,真的不得不說(shuō)劲件,想什么來(lái)什么啊,既然有了現(xiàn)成的輪子约急,我們就直接跑唄零远!
在最上層的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"
}
接下來(lái)配置自己的nexus私服即可
maven {
//替換自己搭建的私服
url "http://127.0.0.1:8081/nexus/content/repositories/releases"
}
通剛才的合并打包方式,最后發(fā)布到自己的nexus私服上
你以為這樣就結(jié)束了嗎厌蔽?并沒有G@薄!奴饮!
實(shí)際操作過(guò)程中纬向,筆者發(fā)現(xiàn),我們本地實(shí)際是有依賴本地第三方的aar包
的戴卜,換句話說(shuō)逾条,并非所有的庫(kù)都是遠(yuǎn)程依賴,你會(huì)發(fā)現(xiàn)投剥,原來(lái)腳本居然會(huì)將本地依賴的aar文件师脂,也合并到pom.xml文件中,繼而發(fā)布到nexus私服上去了江锨,這個(gè)時(shí)候給別人遠(yuǎn)程依賴吃警,就會(huì)一直找不到相關(guān)的本地庫(kù)
如何解決呢?
看來(lái)偷懶是不行的了啄育,還是得改腳本汤徽,經(jīng)過(guò)筆者的觀察,發(fā)現(xiàn)在生成的pom.xml中可以過(guò)濾掉
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)
}
}
}
即把版本號(hào)為空的過(guò)濾掉即可灸撰。
思考與擴(kuò)展
經(jīng)過(guò)一番折騰,好歹也是合并出來(lái)我們想要的東西,但是筆者剛剛也說(shuō)到了浮毯,公司主項(xiàng)目除了自己使用完疫,還是組合成sdk給第三方使用,第三方可能會(huì)改下首頁(yè)的布局债蓝,顏色壳鹤,等等。如何在不侵入主業(yè)務(wù)的情況下饰迹,作變更呢芳誓?其實(shí)很簡(jiǎn)單,借鑒Android中多渠道包的生成啊鸭,同名的資源放在不同的目錄
遺憾的是原生的腳本并不支持這種姿勢(shì)锹淌,我在最上層的merge
模塊中使用同名的資源試圖覆蓋下層的資源,達(dá)到替換的目的赠制,并未得逞B赴凇!钟些!
沒辦法烟号,還是得改腳本,改動(dòng)的思想實(shí)際就是在腳本合并的過(guò)程中政恍,優(yōu)先記錄最上層的資源名稱汪拥,當(dāng)合并下層模塊的資源文件時(shí),直接跳過(guò)即可篙耗,改過(guò)的腳本在文章的末尾迫筑。
混淆配置
關(guān)于混淆的配置,只需要在最上層的merge模塊中配置即可
注意事項(xiàng)
1.盡量不要使用原本的腳本文件鹤树,因?yàn)樵髡咭呀?jīng)幾年未更新過(guò)铣焊,文章末尾有筆者的改動(dòng)過(guò)的腳本文件
2.各個(gè)組件的清單會(huì)合并,不需要在最上層的組件中統(tǒng)一注冊(cè)
3.本地依賴的jar包不用擔(dān)心罕伯,因?yàn)槟_本會(huì)合并到最終aar庫(kù)的lib目錄下
4.本地依賴的aar包曲伊,要記得隨著遠(yuǎn)程依賴,給第三方一起依賴追他,即第三方除了依賴我們的遠(yuǎn)程依賴坟募,還需要本地依賴我們所使用的aar文件,這也算是一個(gè)缺陷吧
5.第三方依賴的插件版本最好跟我們合并使用的gradle版本一致
小結(jié)
到目前為止邑狸,合并多組件的幾個(gè)坑基本已經(jīng)走了一遍了懈糯,其實(shí)在去年底,筆者在公司的直播項(xiàng)目中已經(jīng)將組件化落地了单雾,而后在實(shí)現(xiàn)多組件的道路上也踩了不少坑赚哗,本來(lái)這篇文章并沒有打算發(fā)布出來(lái)她紫,因?yàn)椴⒉皇撬腥硕紩?huì)碰到這類需求,但是前段時(shí)間有個(gè)朋友公司問(wèn)了我相關(guān)的問(wèn)題屿储,看他踩坑了很久贿讹,所以還是覺得發(fā)布出來(lái),至少對(duì)于看到的人而言够掠,以后碰到類似問(wèn)題民褂,可以少走些彎路,提高效率疯潭。
紙上得來(lái)終覺淺赊堪,絕知此事要躬行!