十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶(hù) + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專(zhuān)業(yè)推廣+無(wú)憂(yōu)售后,網(wǎng)站問(wèn)題一站解決
本篇內(nèi)容介紹了“如何理解Android架構(gòu)”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
成都創(chuàng)新互聯(lián)公司是一家專(zhuān)業(yè)提供嵩明企業(yè)網(wǎng)站建設(shè),專(zhuān)注與網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、HTML5建站、小程序制作等業(yè)務(wù)。10年已為嵩明眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專(zhuān)業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。
所有的模塊化都是為了滿(mǎn)足單一設(shè)計(jì)原則 (字面意思理解即可),一個(gè)函數(shù)或者一個(gè)類(lèi)再或者一個(gè)模塊,職責(zé)越單一復(fù)用性就越強(qiáng),同時(shí)能夠間接降低耦合性
在軟件工程的背景下,改動(dòng)就會(huì)有出錯(cuò)的可能,不要說(shuō)"我注意一點(diǎn)就不會(huì)出錯(cuò)"這種話(huà),因?yàn)槿瞬皇菣C(jī)器。我們能做的就是盡可能讓模塊更加單一,職責(zé)越單一影響到外層模塊的可能性就越小,這樣出錯(cuò)的概率也就越低。
所以模塊化核心思想即:?jiǎn)我辉O(shè)計(jì)原則
做模塊化處理的時(shí)候盡量基于兩種特性進(jìn)行功能特性、業(yè)務(wù)特性
功能特性
網(wǎng)絡(luò)、圖片加載等等都可稱(chēng)之為功能特性。比如網(wǎng)絡(luò):我們可以將網(wǎng)絡(luò)框架的集成、封裝等等寫(xiě)到同一個(gè)模塊(module、package等)當(dāng)中,這樣可以增強(qiáng)可讀性(同一目錄一目了然)、降低誤操作概率,方便于維護(hù)也更加安全。同時(shí)也可將模塊托管至遠(yuǎn)程如maven庫(kù),可供多個(gè)項(xiàng)目使用,進(jìn)一步提升復(fù)用性
業(yè)務(wù)特性
業(yè)務(wù)特性字面意思理解即可,就是我們常常編寫(xiě)的業(yè)務(wù),需要以業(yè)務(wù)的特性進(jìn)行模塊劃分
為什么說(shuō)業(yè)務(wù)特性?xún)?yōu)先級(jí)要高于功能特性?
舉個(gè)例子如下圖:
相信很多人見(jiàn)過(guò)或者正在使用這種分包方式,在業(yè)務(wù)層把所有的Adapter、Presenter、Activity等等都放在對(duì)應(yīng)的包中,這種方式合理嗎?先說(shuō)答案不合理,首先這已經(jīng)是在業(yè)務(wù)層,我們做的所有事情其實(shí)都在為業(yè)務(wù)層服務(wù),所以業(yè)務(wù)的優(yōu)先級(jí)應(yīng)該是最高的,我們應(yīng)當(dāng)優(yōu)先根據(jù)業(yè)務(wù)特性將對(duì)應(yīng)的類(lèi)放入到同一個(gè)包中。
功能模塊核心是功能,應(yīng)當(dāng)以功能進(jìn)行模塊劃分。業(yè)務(wù)模塊核心是業(yè)務(wù),應(yīng)當(dāng)優(yōu)先以業(yè)務(wù)進(jìn)行模塊劃分,其次再以功能進(jìn)行模塊劃分。
前端開(kāi)發(fā)其實(shí)就是做數(shù)據(jù)搬運(yùn),再展示到視圖中。數(shù)據(jù)與視圖是兩個(gè)不同的概念,為了提高復(fù)用性以及可維護(hù)性,我們應(yīng)當(dāng)根據(jù)單一設(shè)計(jì)原則我們應(yīng)當(dāng)將二者進(jìn)行分層處理,所以無(wú)論是MVC、MVP還是MVVM最核心的點(diǎn)都是將數(shù)據(jù)與視圖進(jìn)行分層。
絆腳石:
通常來(lái)講,我們通過(guò)網(wǎng)絡(luò)請(qǐng)求拿到數(shù)據(jù)結(jié)構(gòu)都是后端定義的,這也就意味著視圖層不得不直接使用后端定義的字段,一旦后端進(jìn)行業(yè)務(wù)調(diào)整會(huì)迫使我們前端從數(shù)據(jù)層-->視圖層都會(huì)進(jìn)行對(duì)應(yīng)的改動(dòng),如下偽代碼所示:
//原始邏輯 數(shù)據(jù)層 Model{ title } UI層 View{ textView = model.title } //后端調(diào)整后 數(shù)據(jù)層 Model{ title prefix } UI層 View{ textView = model.prefix + model.title }
起初我們的textView顯示的是model中的title,但后端調(diào)整后我們需要在model中加一個(gè)prefix字段,同時(shí)textView顯示內(nèi)容也要做一次字符串拼接。視圖層因?yàn)閿?shù)據(jù)層的改動(dòng)而被動(dòng)做了修改。既然做了分層我們想要的肯定是視圖、數(shù)據(jù)互不干擾,如何解決?往下看...
Data Mapper是后端常用的一個(gè)概念,一般情況下他們是不會(huì)直接使用數(shù)據(jù)庫(kù)里面的字段,而是加一個(gè)Data Mapper(數(shù)據(jù)映射)將數(shù)據(jù)庫(kù)表轉(zhuǎn)按需換成Java Bean,這樣做的好處也很明顯,表結(jié)構(gòu)甭管怎么折騰都不會(huì)影響到業(yè)務(wù)層代碼。
對(duì)于前端我覺(jué)得可以適當(dāng)引入Data Mapper,將后端數(shù)據(jù)轉(zhuǎn)換成本地模型,本地模型只與設(shè)計(jì)圖對(duì)應(yīng),將后端業(yè)務(wù)與視圖完全隔離。這也就解決了 1.3 面臨的問(wèn)題,具體方式如下:
數(shù)據(jù)層 Model{ title prefix } 本地模型(與設(shè)計(jì)圖一一對(duì)應(yīng)) LocalModel{ //將后端模型轉(zhuǎn)換為本地模型 title = model.prefix + model.title } UI層 View{ textView = localModel.title }
LocalModel相當(dāng)于一個(gè)中間層,通過(guò)適配器模式將數(shù)據(jù)層與視圖層做隔離。
前端引入Data Mapper后可以脫離后端進(jìn)行開(kāi)發(fā),只要需求明確就可以做視圖層的開(kāi)發(fā),完全不需要擔(dān)心后端返回什么結(jié)構(gòu)、字段。并且這種做法是一勞永逸的,比如后端需要對(duì)某些字段做調(diào)整,我們可以不暇思索直奔數(shù)據(jù)層,涉及到的調(diào)整100%不會(huì)影響到視圖層
注意點(diǎn):
當(dāng)下有一部分公司為了將前后端分離更徹底,由前端開(kāi)發(fā)人員提供Java Bean(相當(dāng)于LocalModel)的結(jié)構(gòu),好處也很明顯,更多的業(yè)務(wù)內(nèi)聚到后端,很大程度提升了業(yè)務(wù)的靈活性,畢竟App發(fā)一次版成本還是比較大的。面對(duì)這種情況我們其實(shí)沒(méi)必要再編寫(xiě)Data Mapper。所以任何架構(gòu)設(shè)計(jì)都要結(jié)合實(shí)際情況,適合自己的才是最好的。
關(guān)于業(yè)務(wù)邏輯其實(shí)是一個(gè)很籠統(tǒng)的概念,甚至可以將任意一行代碼稱(chēng)之為業(yè)務(wù)邏輯,如此寬泛的概念我們?cè)撊绾稳ダ斫?我先大致將它分為兩個(gè)方面:
界面交互邏輯:視圖層的交互邏輯,比如手勢(shì)控制、吸頂懸浮等等都是根據(jù)業(yè)務(wù)需要實(shí)現(xiàn)的,所以嚴(yán)格來(lái)說(shuō)這部分也屬于業(yè)務(wù)邏輯。但這部分業(yè)務(wù)邏輯一般在視圖層實(shí)現(xiàn)。
數(shù)據(jù)邏輯:這部分是大家常說(shuō)的業(yè)務(wù)邏輯,屬于強(qiáng)業(yè)務(wù)邏輯,比如根據(jù)不同用戶(hù)類(lèi)型獲取不同數(shù)據(jù)、展示不同界面,加上Data Mapper一系列操作其實(shí)就是給后端兜底,幫他們補(bǔ)全剩余邏輯而已。為了方便大家理解下文我將數(shù)據(jù)邏輯統(tǒng)稱(chēng)為業(yè)務(wù)邏輯。
前面我們說(shuō)到,Android開(kāi)發(fā)應(yīng)該具備數(shù)據(jù)層跟視圖層,那業(yè)務(wù)邏輯放在哪一層比較合適呢?比如MVVM模式下大家都說(shuō)將業(yè)務(wù)邏輯放到ViewModel處理,這么說(shuō)也沒(méi)有太大的問(wèn)題,但如果一個(gè)界面足夠復(fù)雜那對(duì)應(yīng)的ViewModel代碼可能會(huì)有成百上千行,看起來(lái)會(huì)很臃腫可讀性也非常差。最重要的一點(diǎn)這些業(yè)務(wù)很難編寫(xiě)單元測(cè)試用例。
關(guān)于業(yè)務(wù)邏輯我建議單獨(dú)寫(xiě)一個(gè)use case處理。
use case通常放在ViewModel/Presenter與數(shù)據(jù)層之間,業(yè)務(wù)邏輯以及Data Mapper都應(yīng)該放在use case中,每一個(gè)行為對(duì)應(yīng)一個(gè)use case。這樣就解決了ViewModel/Presenter臃腫的問(wèn)題,同時(shí)更方便編寫(xiě)測(cè)試用例。
注意點(diǎn):
好的設(shè)計(jì)都是特定場(chǎng)景解決特定問(wèn)題,過(guò)度設(shè)計(jì)不僅解決不了任何問(wèn)題反而會(huì)增加開(kāi)發(fā)成本。以我目前經(jīng)驗(yàn)來(lái)看Android開(kāi)發(fā)至少一半的場(chǎng)景都很簡(jiǎn)單:請(qǐng)求-->拿數(shù)據(jù)-->渲染視圖最多再加個(gè)Data Mapper,流程很單一并且后期改動(dòng)的可能也不太大,這種情況就沒(méi)必要寫(xiě)一個(gè)use case,Data Mapper扔到數(shù)據(jù)層即可。
先說(shuō)結(jié)論:數(shù)據(jù)驅(qū)動(dòng)UI的本質(zhì)是控制反轉(zhuǎn)
控制即對(duì)程序流程的控制,一般由我們開(kāi)發(fā)者承擔(dān),此過(guò)程為控制。但開(kāi)發(fā)者是人所以不可避免出現(xiàn)錯(cuò)誤,此時(shí)可以將角色做一個(gè)反轉(zhuǎn)由成熟的框架負(fù)責(zé)整個(gè)流程,程序員只需要在框架預(yù)留的擴(kuò)展點(diǎn)上,添加跟自己的業(yè)務(wù)代碼,就可以利用框架來(lái)驅(qū)動(dòng)整個(gè)程序流程的執(zhí)行,此過(guò)程為反轉(zhuǎn)。
控制反轉(zhuǎn)概念和設(shè)計(jì)原則中的依賴(lài)倒置很相似,只是少了一個(gè)依賴(lài)抽象。
打個(gè)比方:
現(xiàn)有一個(gè)HTTP請(qǐng)求的需求,如果想自己維護(hù)HTTT鏈接、自己管理TCP Socket、自己處理HTTP緩存.....就是整個(gè)HTTP協(xié)議全部自己封裝,先不說(shuō)這個(gè)工程能不能靠個(gè)人實(shí)現(xiàn),就算實(shí)現(xiàn)也是漏洞百出,此時(shí)可以換個(gè)思路:通過(guò)OkHttp去實(shí)現(xiàn),OkHttp是一個(gè)成熟的框架用它基本上不會(huì)出錯(cuò)。個(gè)人封裝HTTP協(xié)議到使用OkHttp框架,這個(gè)過(guò)程在控制HTTP的角色上發(fā)生了一個(gè)反轉(zhuǎn),個(gè)人--->成熟的框架OkHttp即控制反轉(zhuǎn),好處也很明顯,框架出錯(cuò)的概率遠(yuǎn)低于個(gè)人。
通俗一點(diǎn)說(shuō)就是當(dāng)數(shù)據(jù)改變時(shí)對(duì)應(yīng)的UI也要跟著變,反過(guò)來(lái)說(shuō)當(dāng)需要改變UI只需要改變對(duì)應(yīng)的數(shù)據(jù)即可。現(xiàn)在比較流行的UI框架如Flutter、Compose、Vue其本質(zhì)都是基于函數(shù)式編程實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI,它們共同的目的都是為了解決數(shù)據(jù),UI一致性問(wèn)題。
在當(dāng)前的Android中可以使用DataBinding實(shí)現(xiàn)同樣的效果,以Jetpack MVVM為例:ViewModel從Repository拿到數(shù)據(jù)暫存到ViewModel對(duì)應(yīng)的ObservableFiled即可實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI,但前提是從Repository拿到的數(shù)據(jù)可以直接用,如果在A(yíng)ctivity或者Adapter做數(shù)據(jù)二次處理再notify UI,已經(jīng)違背數(shù)據(jù)驅(qū)動(dòng)UI核心思想。所以想實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI必須要有合理的分層(UI層拿到的數(shù)據(jù)無(wú)需處理,可以直接用),Data Mapper恰好解決這一問(wèn)題,同時(shí)也可規(guī)避大量編寫(xiě)B(tài)indAdapter的現(xiàn)狀。
DataBinding并非函數(shù)式編程,它只是通過(guò)AbstractProcessor生成中間代碼,將數(shù)據(jù)映射到XML中
當(dāng)前Android生態(tài)能實(shí)現(xiàn)數(shù)據(jù)綁定UI的框架只有兩個(gè):DataBinding、Compose(暫不討論)
在引入DataBinding之前渲染一條數(shù)據(jù)通常需要兩步,如下:
var title = "iOS" fun setTitle(){ //第一步更改數(shù)據(jù)源 title = "Android" //第二個(gè)更改UI textView = title }
共需要兩步更改數(shù)據(jù)源、更改UI,數(shù)據(jù)源跟UI有一個(gè)忘記修改便會(huì)出現(xiàn)BUG,千萬(wàn)不要說(shuō):“兩個(gè)我都不會(huì)忘記修改”,當(dāng)面臨復(fù)雜的邏輯以及十幾個(gè)甚至幾十個(gè)的數(shù)據(jù)源很難保證不出錯(cuò)。這種問(wèn)題可以通過(guò)DataBinding解決,只需更改對(duì)應(yīng)的ObservableFiledUI便會(huì)同步修改,控制UI狀態(tài)也從個(gè)人反轉(zhuǎn)到的DataBinding,個(gè)人疏忽的事情DataBinding可不會(huì)。
所以說(shuō)數(shù)據(jù)驅(qū)動(dòng)UI底層思想是控制反轉(zhuǎn)
引入diff之前:
RecyclerView想要實(shí)現(xiàn)動(dòng)態(tài)刪除、添加、更新需要分別手動(dòng)更新數(shù)據(jù)和UI,這樣在中間插了一道并且分別更新數(shù)據(jù)和UI已經(jīng)違背了前面所說(shuō)的數(shù)據(jù)驅(qū)動(dòng)UI,而我們想要的是不管刪除、添加或者更新只有一個(gè)入口,只要改變數(shù)據(jù)源就會(huì)驅(qū)動(dòng)UI做更新,想要滿(mǎn)足這一原則只能改變數(shù)據(jù)源后對(duì)RecyclerView做全部刷新,但這樣會(huì)造成性能問(wèn)題,復(fù)雜的界面會(huì)感到明顯的卡頓。
引入diff之后:
Diff算法通過(guò)對(duì)oldItem和newItem做差異化比對(duì),會(huì)自動(dòng)更新改變的item,同時(shí)支持刪除、添加的動(dòng)畫(huà)效果,這一特性解決了RecyclerView需要實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)UI的性能問(wèn)題
一個(gè)入口,一個(gè)出口。
不在函數(shù)鏈內(nèi)部執(zhí)行與運(yùn)算本身無(wú)關(guān)的操作
不在函數(shù)鏈內(nèi)部使用外部變量(實(shí)際上這一條很難遵守,可以適當(dāng)突破)
說(shuō)的通俗點(diǎn)就是給定一個(gè)初始值,經(jīng)過(guò)函數(shù)鏈的運(yùn)行會(huì)得到一個(gè)目標(biāo)值,運(yùn)算的過(guò)程中外部沒(méi)有插手的權(quán)限,同時(shí)不做與本身無(wú)關(guān)的操作,從根本上解決了不可預(yù)期錯(cuò)誤的產(chǎn)生。
舉個(gè)例子:
//Kotlin代碼 listOf(10, 20).map { it + 1 }.forEach { Log.i("list", "$it") }
上面這種鏈?zhǔn)骄幊叹褪菢?biāo)準(zhǔn)的函數(shù)式編程,輸入到輸出之間開(kāi)發(fā)者根本沒(méi)有插手的機(jī)會(huì)(即Log.i(..)之前開(kāi)發(fā)者沒(méi)有權(quán)限處理list),所以整個(gè)流程是100%安全的,RxJava、Flow、鏈?zhǔn)礁唠A函數(shù)都是標(biāo)準(zhǔn)的函數(shù)式編程,它們從規(guī)范層面解決數(shù)據(jù)安全問(wèn)題。所以我建議在Kotlin中 碰到數(shù)據(jù)處理盡量使用鏈?zhǔn)礁唠A函數(shù)(RxJava、Kotlin Flow亦然)。
Android視圖開(kāi)發(fā)大都遵循如下流程:請(qǐng)求-->處理數(shù)據(jù)-->渲染UI,這一流程可以借鑒函數(shù)式編程,將請(qǐng)求作為入口,渲染做為出口,在這個(gè)流程中盡量不做與當(dāng)前行為無(wú)關(guān)的事(這也要求ViewModel,Repository中的函數(shù)要符合單一原則)。這樣說(shuō)有點(diǎn)籠統(tǒng),下面舉個(gè)反例:
View{ //刷新 fun refresh(){ ViewModel.load(true) } //加載更多 fun loadMore(){ ViewModel.load(false) } } ViewModel{ //加載數(shù)據(jù) load(isRefresh){ if (isRefresh){ //刷新 }else{ //加載更多 } } }
View層有刷新、加載更多兩種行為,load(isRefresh)一個(gè)入口,兩個(gè)出口。面臨的問(wèn)題很明顯,修改刷新或加載更多都會(huì)對(duì)對(duì)方產(chǎn)生影響,違反開(kāi)閉原則中的閉(對(duì)修改關(guān)閉:行為沒(méi)變不準(zhǔn)修改源代碼),導(dǎo)致存在不可預(yù)期的問(wèn)題產(chǎn)生??梢越梃b函數(shù)式編程思想對(duì)其進(jìn)行改進(jìn),將ViewModel的load函數(shù)拆分成refresh和loadMore,這樣刷新和加載更多兩種行為、兩個(gè)入口、兩個(gè)出口互不干涉,通過(guò)函數(shù)的銜接形成兩條獨(dú)立的業(yè)務(wù)鏈條。
函數(shù)式編程可以約束我們寫(xiě)出規(guī)范的代碼,面對(duì)不能使用函數(shù)式編程的場(chǎng)景,我們可以嘗試自我約束往函數(shù)式編程方向靠攏,大致也能實(shí)現(xiàn)相同的效果。
“如何理解Android架構(gòu)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!