什么是組件化
組件(Component)是對數(shù)據(jù)和方法的簡單封裝碍沐,功能單一狸捅,高內(nèi)聚,并且是業(yè)務(wù)能劃分的最小粒度累提。
組件化是基于組件可重用的目的上尘喝,將一個大的軟件系統(tǒng)按照分離關(guān)注點的形式,拆分成多個獨立的組件斋陪,使得整個軟件系統(tǒng)也做到電路板一樣朽褪,是單個或多個組件元件組裝起來置吓,哪個組件壞了,整個系統(tǒng)可繼續(xù)運行缔赠,而不出現(xiàn)崩潰或不正逞苊現(xiàn)象,做到更少的耦合和更高的內(nèi)聚嗤堰。
區(qū)分模塊化與組件化
模塊化
模塊化就是將一個程序按照其功能做拆分戴质,分成相互獨立的模塊,以便于每個模塊只包含與其功能相關(guān)的內(nèi)容踢匣,模塊我們相對熟悉,比如登錄功能可以是一個模塊,搜索功能可以是一個模塊等等置森。組件化
組件功能單一,高內(nèi)聚符糊,是業(yè)務(wù)能劃分的最小粒度凫海。組件化就是更關(guān)注可復用性,更注重關(guān)注點分離男娄,如果從集合角度來看的話行贪,可以說往往一個模塊包含了一個或多個組件,或者說模塊是一個容器模闲,由組件組裝而成建瘫。
組件可以單獨成獨立項目進行編譯運行,通常每個組件由單人或者固定人員負責尸折,別人可能不清楚這個組件下的業(yè)務(wù)及代碼啰脚,因為這些組件可以做成遠程倉庫依賴的方式。
組件化優(yōu)勢
1.提高編譯速度实夹,從而提高并行開發(fā)效率
2.每個組件有自己獨立的版本橄浓,可以獨立編譯、測試亮航、打包和部署
3.避免模塊之間的交叉依賴荸实,做到低耦合、高內(nèi)聚
4.組件之間可以靈活組建缴淋,快速生成不同類型的定制產(chǎn)品准给,可拆可裝
組件化需要考慮問題
-
組件分層
如何將龐大工程分成有機整體。
-
組件之間跳轉(zhuǎn)
組件之間是無法相互引用的重抖,所以做跳轉(zhuǎn)和通信需要做處理露氮,實現(xiàn)辦法就是路由,業(yè)務(wù)組件之間相互通信钟沛,需要訪問基礎(chǔ)組件中的路由框架畔规,路由來尋址找到目標頁面或者目標功能。
什么是路由讹剔?
app的一個頁面就可以類比于一個個網(wǎng)站里面的頁面油讯,瀏覽器的每個頁面由url定義详民,給不同url傳遞不同參數(shù),頁面的表現(xiàn)形式還稍有不通過陌兑,這里的映射關(guān)系就是url對應(yīng)頁面沈跨,每個app的每個頁面也可以類比于網(wǎng)站的頁面,那是不是可以采用url的方式來定義每個頁面呢兔综?這樣是不是也就有了url對應(yīng)app頁面的映射關(guān)系饿凛,如果有了這樣的映射關(guān)系,給定一個url软驰,那是不是就可以知道跳轉(zhuǎn)到某一個具體的Activity了涧窒?Android路由也是一個映射表,用來映射Uri和對應(yīng)的頁面跳轉(zhuǎn)锭亏,這個url就是組件名+頁面名來拼接纠吴。
我這里做跳轉(zhuǎn)用的是ARouter。這里是我的另一篇ARouter解析慧瘤。
-
組件化解決重復依賴
組件從開始設(shè)計的時候就需要嚴格分好依賴層級戴已,組件之間不可相互依賴,不可重復依賴锅减,業(yè)務(wù)組件只可依賴必須的base組件糖儡。主app殼組件依賴其他所有組件。
-
組件單獨運行和集成測試
只需要把 Apply plugin: 'com.android.library' 切換成Apply plugin: 'com.android.application' 怔匣。我們可以通過 Gradle腳本配置方式握联,修改properties配置可讓某個組件單獨運行。比如使用isDebug變量對兩種引用插件進行選擇每瞒。
集成測試需要空manifest金闽,獨立運行需要完善manifest文件,需要用gradle配置進行2個manifest文件切換独泞。
然后將公共配置的gradle代碼抽出配置文件進行apply使用呐矾。
-
組件化時資源名沖突
color,shape懦砂,drawable,圖片資源组橄,布局資源荞膘,或者anim資源等等,都有可能造成資源名稱沖突玉工。大家都在不同組件下羽资,通常不會交流,有可能造成沖突遵班,所以在項目創(chuàng)建初期屠升,需要定義好公共資源潮改,很少修改。在版本不斷升級腹暖,業(yè)務(wù)不斷復雜汇在,肯定還是避免不了不同的資源文件,所以需要要有按模塊區(qū)分命名規(guī)則脏答。
-
組件之間相互調(diào)用
組件之間相互通信是少不了的糕殉,各組件間不能直接調(diào)用。組件之間的交互如果還是直接引用的話殖告,那么組件之間根本沒有做到解耦阿蝶。
需要暴露功能給別的組件調(diào)用的組件,需要在公共模塊base里面去聲明接口黄绩,繼承ARouter庫中的Iprovider接口羡洁。然后在自己模塊中實現(xiàn)該接口的功能。別的模塊直接調(diào)用該暴露的接口而實現(xiàn)功能爽丹。
在公共模塊給各個組件定義一個包焚廊,里面創(chuàng)建需要提供給外部使用的接口SwitchFarmProvider,需要繼承ARouter提供的IProvider接口习劫。
interface SwitchFarmProvider: IProvider {
fun showSwitchFarmDialog(pos: Int, fragmentManager: FragmentManager)
}
然后在自己組件中去實現(xiàn)該方法的功能咆瘟,這樣就能提供給外部組件調(diào)用內(nèi)部的方法,而不用將該功能將低到公共組件中诽里。
@Route(path = "/xxx/xxx", name = "xxx")
class SwitchFarmImp: SwitchFarmProvider {
override fun showSwitchFarmDialog(pos: Int, fragmentManager: FragmentManager) {
SwitchFarmAllDialog().switchTab(pos).show(fragmentManager, "")
}
override fun init(context: Context?) {
}
}
調(diào)用方式袒餐,通過ARouter提供的ARouter.getInstance().navigation(Class)方法獲取該實現(xiàn)類,調(diào)用其公共方法谤狡。
ARouter.getInstance().navigation(SwitchFarmProvider.class).showSwitchFarmDialog(2, getFragmentManager());
-
將組件發(fā)布到遠程倉庫
不同部門的開發(fā)分工更加明確之后灸眼,不屬于自己維護的組件范圍不能隨意地修改∧苟基本上自己負責自己模塊下的組件焰宣,盡可能少地改動別的組件代碼。這一塊的配置是全文終點捕仔,敲黑板了匕积。
我這里將各個組件發(fā)布到阿里云 maven庫中,發(fā)布方法見我另一篇文章——發(fā)布開源庫到阿里云 maven倉庫榜跌。發(fā)布之后闪唆,可以看到遠程倉庫里的庫。這里需要注意的是钓葫,組件不要依賴本地組件悄蕾,而是從底層開始逐漸依賴,按照依賴順序上傳础浮,否則很可能會依賴錯誤帆调。
然后各個模塊引入庫奠骄,在app下都引入遠程依賴,在settings.gradle中移除各個組件的include番刊。那么項目文件夾就變?yōu)榱藷o本地依賴的狀態(tài):
此時你的項目仍然是能夠運行起來的含鳞,不過編譯運行的代碼就不是你本地的了,而是直接運行各個遠程的aar包撵枢。
那么平時開發(fā)怎么修改我們本地的代碼呢民晒?做配置,可分別設(shè)置某個組件是依賴本地還是遠程锄禽,依賴本地的組件可盡心開發(fā)修改潜必,發(fā)布上傳新的版本。
做法是在各組件下新建gradle.properties讀取里面的配置沃但,比如設(shè)置true表示依賴遠程磁滚。在settings.gradle中讀取該文件的屬性,看是否需要依賴本地的組件宵晚。在項目的build.gradle中配置垂攘,讀取該true/false屬性,判斷是依賴本地庫還是遠程庫淤刃。
settings.gradle中配置:
includeCompat ':module-play'
includeCompat ':module-notice'
includeCompat ':module-community'
includeCompat ':module-user'
includeCompat ':module-login'
includeCompat ':module-home'
includeCompat ':module-entrance'
includeCompat ':library-res'
includeCompat ':library-network'
includeCompat ':library-base'
includeCompat ':app'
rootProject.name = "MvvmFrame"
def includeCompat(String name) {
if (!isMaven(name)) {
include(name)
}
}
def isMaven(String name) {
println("isMaven" + name)
Properties properties = new Properties()
def file = new File("${name.replace(":", "")}/maven.properties")
if (file.exists()) {
InputStream inputStream = file.newDataInputStream()
properties.load(inputStream)
def str = properties.getProperty('MAVEN')
if (str == null) {
return false
} else {
return Boolean.parseBoolean(str)
}
}
return false
}
項目的build.gradle中配置:
//根據(jù)是否為遠程依賴設(shè)置依賴遠程庫還是本地庫
ext.projectCompat = { name ->
def realName = name.replace(":", "")
if (isMaven(realName)) {
return "com.libo:${realName}:${mavenVersion(realName)}"
} else {
return project(name)
}
}
//判斷當前組件是否為遠程依賴
ext.isMaven = { name ->
Properties properties = new Properties()
def file = rootProject.file("${name}/maven.properties")
if (file.exists()) {
InputStream inputStream = file.newDataInputStream()
properties.load(inputStream)
def str = properties.getProperty('MAVEN')
if (str == null) {
return false
} else {
return Boolean.parseBoolean(str)
}
}
return false
}
//讀取maven.property文件中庫版本
ext.mavenVersion = { name ->
println("mavenVersion::${name}")
Properties properties = new Properties()
def file = rootProject.file("${name}/maven.properties")
if (file.exists()) {
InputStream inputStream = file.newDataInputStream()
properties.load(inputStream)
def str = properties.getProperty('VERSION')
if (str == null) {
throw Exception(file.path + " VERSION == null")
} else {
return str
}
}
return ""
}
app下的build.gradle中這樣依賴晒他,判斷是依賴遠程還是本地。
implementation projectCompat(":library-res")
implementation projectCompat(":library-base")
implementation projectCompat(":library-network")
implementation projectCompat(":module-entrance")
implementation projectCompat(":module-home")
implementation projectCompat(":module-community")
implementation projectCompat(":module-notice")
implementation projectCompat(":module-user")
implementation projectCompat(":module-login")
implementation projectCompat(":module-play")
該項目配置的github地址逸贾,需要細看的戳這里
參考:
https://www.bilibili.com/video/BV1oK4y1R7Hx?p=9&vd_source=40c24e77b23dc2e50de2b7c87c6fed59