我們知道沼溜,軟件開(kāi)發(fā)項(xiàng)目是一個(gè)綜合平衡的過(guò)程丹喻,要平衡時(shí)間、成本俊性、范圍搪桂、質(zhì)量四個(gè)要素透敌,在單個(gè)項(xiàng)目中,這四要素是非此即彼的:時(shí)間緊迫就要壓縮需求范圍踢械,添加需求就要追加成本酗电,確保質(zhì)量就不能過(guò)于壓縮工期,相互之間無(wú)法調(diào)和内列。
但如果跳出單個(gè)項(xiàng)目撵术,在日常積累上面下功夫,我們卻有可能找到一種同時(shí)有利于項(xiàng)目四要素的途徑德绿,就是建立和使用通用的開(kāi)發(fā)架構(gòu)荷荤。
大部分公司不會(huì)僅研發(fā)一個(gè)App,而是會(huì)研發(fā)一系列App移稳,形成家族化蕴纳、品牌化,或互相依賴(lài)个粱,或入場(chǎng)試錯(cuò)古毛,這些App功能業(yè)務(wù)可能不盡相同,但一般都需要網(wǎng)絡(luò)模塊/日志模塊/圖像加載模塊,需要一些常見(jiàn)的簡(jiǎn)單函數(shù)稻薇,為建立品牌形象嫂冻,還需要統(tǒng)一的主題資源如色調(diào)/圖標(biāo)/提示語(yǔ)等。
這些低水平的重復(fù)開(kāi)發(fā)塞椎,是與業(yè)務(wù)沒(méi)有直接關(guān)系卻必須支付的“死重”桨仿,是可以通過(guò)模塊復(fù)用來(lái)提升效率的,這也是我們做Android架構(gòu)分層的初衷案狠,我們把開(kāi)發(fā)中常用的模塊抽象出來(lái)服傍,分組分層,形成結(jié)構(gòu)清晰骂铁,組裝靈活的通用組件庫(kù)吹零,支撐起了多個(gè)App的快速實(shí)現(xiàn)與迭代。
image
價(jià)值
在實(shí)際開(kāi)發(fā)過(guò)程中拉庵,我們發(fā)現(xiàn)通用組件庫(kù)對(duì)于開(kāi)發(fā)的效率和質(zhì)量灿椅,都有了顯著的提升:
節(jié)省時(shí)間,因?yàn)榻M件功能可以復(fù)用钞支,能降低團(tuán)隊(duì)成員熟悉項(xiàng)目的成本茫蛹,為新業(yè)務(wù)開(kāi)發(fā)提供基礎(chǔ),加快開(kāi)發(fā)迭代速度伸辟,有利于更快地發(fā)布版本麻惶。
降低成本,把穩(wěn)定的公共模塊抽象為通用組件庫(kù)信夫,提供給各個(gè)業(yè)務(wù)線(xiàn)協(xié)作使用窃蹋,能在全公司范圍內(nèi)減少重復(fù)開(kāi)發(fā)和升級(jí)維護(hù)的工作量
提升質(zhì)量,頻繁使用的功能/業(yè)務(wù)模塊采用組件復(fù)用方式静稻,更有利于暴露缺陷警没,一處修改,多處受益振湾,提高產(chǎn)品質(zhì)量杀迹。
image
具體設(shè)計(jì)
對(duì)于App來(lái)說(shuō),選用組件應(yīng)該按需取用押搪,僅選用自己需要的那些組件树酪,這就需要把組件分離為多個(gè),形成一個(gè)結(jié)構(gòu)化的組件庫(kù)大州,我們最終形成的組件庫(kù)大概是這樣的:
在上圖的結(jié)構(gòu)中续语,通用組件是與業(yè)務(wù)無(wú)關(guān)的基礎(chǔ)功能,共享組件是與業(yè)務(wù)有緊密聯(lián)系的厦画,共享組件可能需要引用通用組件疮茄。
在具體實(shí)現(xiàn)中滥朱,我們處理過(guò)這樣幾個(gè)問(wèn)題:
引用形式:在引用形式上,我們有aar和module代碼兩種方式力试,其中aar適合函數(shù)已經(jīng)固定徙邻,不允許擴(kuò)展修改的情況;module適合類(lèi)型已經(jīng)分開(kāi)畸裳,但是函數(shù)并未固定缰犁,可以增加新函數(shù)的情況。
依賴(lài)倒置:在引用第三方庫(kù)時(shí)躯畴,我們禁止直接引用民鼓,App可以直接引用第三方庫(kù),但是組件必須使用自己的接口蓬抄,這樣在第三方庫(kù)升級(jí)或者更換時(shí),不會(huì)影響頂層的app夯到。例如網(wǎng)絡(luò)層必須使用網(wǎng)絡(luò)組件自己定義的callback接口嚷缭,實(shí)際上就是都要依賴(lài)于抽象,不能依賴(lài)具體耍贾。
接口隔離:組件庫(kù)大量使用接口為App服務(wù)阅爽,這要求接口保持互相隔離,盡量把功能拆分到多個(gè)接口里荐开,不能出現(xiàn)大而全的接口付翁。
單一職責(zé):每個(gè)組件僅負(fù)責(zé)一類(lèi)功能,互相之間可以有調(diào)用晃听,但不能出現(xiàn)一個(gè)大而全的組件百侧。
開(kāi)放封閉:組件中的函數(shù)是嚴(yán)禁修改的,可以增加新函數(shù)能扒,但嚴(yán)禁修改已有函數(shù)佣渴,除非是為了消除缺陷。
異常拋出:底層組件有時(shí)候必須做異常捕獲初斑,無(wú)論是Exception還是Error都需要拋出辛润,也就是說(shuō)所有的Throwable都需要向上層拋出,避免應(yīng)用層莫名其妙的發(fā)現(xiàn)流程被打斷见秤,無(wú)法查知底層組件出現(xiàn)的異常砂竖。
質(zhì)量控制:底層組件的場(chǎng)景比較抽象也比較固定,實(shí)際上容易做單元測(cè)試和自動(dòng)化測(cè)試鹃答,為組件開(kāi)發(fā)專(zhuān)門(mén)的自動(dòng)測(cè)試模塊乎澄,甚至出一個(gè)自動(dòng)測(cè)試demo,都是性?xún)r(jià)比很高的投入挣跋。
代碼追溯:對(duì)于module形式共享的組件三圆,實(shí)際上允許開(kāi)發(fā)團(tuán)隊(duì)進(jìn)行擴(kuò)展和修改,但是所有的變更都隱藏著缺陷,所以在組件庫(kù)的代碼提交中舟肉,必須進(jìn)行代碼審查修噪,并注釋代碼修改的時(shí)間、事由路媚、操作人等黄琼,以便在出現(xiàn)缺陷時(shí)進(jìn)行追溯 。
jar包沖突:長(zhǎng)期維護(hù)下來(lái)整慎,必然可能引用多個(gè)版本的第三方庫(kù)脏款,這就會(huì)產(chǎn)生jar包沖突的問(wèn)題,所以有必要在底層建立一個(gè)整合第三方庫(kù)的module裤园,各App共同引用這個(gè)庫(kù)module撤师。
版本分支:有些情況下,某些第三方庫(kù)發(fā)生了大版本的迭代更新拧揽,更新前后的功能變化極大剃盾,導(dǎo)致app無(wú)法完美兼容,這就需要建立版本分支淤袜,使用特定版本的組件庫(kù)痒谴,維持app的研發(fā)需求,直至app重構(gòu)铡羡,或app消亡积蔚。
路由解耦:有些業(yè)務(wù)組件是有Activity的,這些組件之間跳轉(zhuǎn)時(shí)烦周,為了解耦合尽爆,應(yīng)該避免通過(guò)包名和類(lèi)名去跳轉(zhuǎn),可以參考Android的Intent思想论矾,允許通過(guò)action和category一起過(guò)濾教翩,找到跳轉(zhuǎn)目標(biāo),實(shí)際開(kāi)發(fā)中可以做一個(gè)Router贪壳,例如阿里開(kāi)源的ARouter
最終饱亿,App選用組件庫(kù)的結(jié)構(gòu),大概是這樣的: