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