架構(gòu)也因項目而異。不同的項目需求不同,對應的架構(gòu)也會不同。
架構(gòu)分層
API的設計完畢之后。接下來我就會考慮App項目的總體架構(gòu)了。總體怎樣架構(gòu),我也以前做過不少嘗試。
早期的時候,Android就是將全部操作都放在Activity里完畢,包含界面數(shù)據(jù)處理、業(yè)務邏輯處理、調(diào)用API。
后來發(fā)現(xiàn)Activity越來越臃腫,代碼越來越復雜,非常難維護。于是就開始思考怎樣拆分,怎樣才干做到松耦合高內(nèi)聚。
前面也說過,一個App的核心就是數(shù)據(jù),那么,從App對數(shù)據(jù)處理的角色劃分出發(fā),最簡單的劃分就是:數(shù)據(jù)管理、數(shù)據(jù)加工、數(shù)據(jù)展示。
對應的也就有了三層架構(gòu):數(shù)據(jù)層、業(yè)務層、展示層。
它們之間的關(guān)系例如以下圖。數(shù)據(jù)層是三層中的最底層。往下,它接入API;往上。它向業(yè)務層交付數(shù)據(jù)。業(yè)務層夾在三層中間,屬于數(shù)據(jù)的加工廠,將數(shù)據(jù)層提供上來的數(shù)據(jù)加工成展示層須要展示的數(shù)據(jù)。展示層處于三層中的最上層,主要就是將從業(yè)務層取得的數(shù)據(jù)展示到界面上。
數(shù)據(jù)層
數(shù)據(jù)層是數(shù)據(jù)管理者。主要任務就是封裝API,并將數(shù)據(jù)結(jié)果交付給上層,中間會再加個數(shù)據(jù)緩存。
整個主流程例如以下圖:
- 業(yè)務層向數(shù)據(jù)層請求數(shù)據(jù);
- 數(shù)據(jù)層檢查緩存中有沒有請求須要的數(shù)據(jù);
- 假設有緩存數(shù)據(jù),則直接返回緩存數(shù)據(jù);
- 假設沒有緩存數(shù)據(jù),則從網(wǎng)絡API獲取數(shù)據(jù)。并將數(shù)據(jù)增加緩存。然后返回數(shù)據(jù)。
調(diào)用網(wǎng)絡API時。還要推斷網(wǎng)絡狀態(tài),依據(jù)不同狀態(tài)做不同處理。假設網(wǎng)絡不可用。就無需發(fā)起請求了。網(wǎng)絡可用時,也要區(qū)分是連接WIFI還是連接移動網(wǎng)絡。連接移動網(wǎng)絡時,一般須要限制調(diào)用比較耗流量的請求。
以前,我們沒有對移動網(wǎng)絡狀態(tài)下的請求進行限制,結(jié)果,測試時流量DuangDuangDuang地一下子就不見了十幾M。連接WIFI時,則無需設置這樣的限制,并且還能夠預先請求一些接口,比方請求當前分頁數(shù)據(jù)時,能夠?qū)⑾乱豁摰臄?shù)據(jù)也預先請求。
緩存也須要緩存策略,不同的接口須要做不同的緩存處理。
首先,緩存僅僅適用于獲取數(shù)據(jù)的接口。對于改動數(shù)據(jù)的接口則不適用。
其次,不同接口緩存時間一般也不同。對于非常少變動的數(shù)據(jù)緩存時間能夠設置長一些,而頻繁變動的數(shù)據(jù)緩存時間則比較短。甚至不進行緩存。
最后,緩存數(shù)據(jù)由于比較多,我們一般保存在數(shù)據(jù)庫。而對于調(diào)用頻率高、最新的數(shù)據(jù),還會在內(nèi)存中也擁有一份緩存,只是緩存時間比較短。
請求緩存數(shù)據(jù)時。會先檢查內(nèi)存緩存中有沒有。有則直接將緩存的數(shù)據(jù)返回,沒有才從數(shù)據(jù)庫獲取。
那么,怎樣將數(shù)據(jù)交付給業(yè)務層呢?這是整個數(shù)據(jù)層模塊與外部交互的部分。當與外部交互的時候,一般都要符合面向接口編程的原則,因此僅僅要提供開放的數(shù)據(jù)接口就能夠了。對于接口的參數(shù)須要說明一下,上面提到的參數(shù)有appKey、version、currentPage這幾個。還有簽名sign、時間戳time,事實上能夠分為兩類:系統(tǒng)參數(shù)和業(yè)務參數(shù)。
像appKey、version、sign、time這些屬于系統(tǒng)參數(shù)。而currentPage,或username之類的則屬于業(yè)務參數(shù)。數(shù)據(jù)層開放的數(shù)據(jù)接口的參數(shù)僅僅須要包括業(yè)務參數(shù)就能夠了,業(yè)務層并不須要關(guān)心系統(tǒng)參數(shù)是什么。系統(tǒng)參數(shù)在數(shù)據(jù)層內(nèi)部封裝API時指定就能夠了。
業(yè)務層
業(yè)務層是數(shù)據(jù)加工者,主要就是從數(shù)據(jù)層獲取數(shù)據(jù)。然后經(jīng)過業(yè)務邏輯處理后轉(zhuǎn)化成展示層須要的數(shù)據(jù)。
業(yè)務層由于夾在數(shù)據(jù)層和展示層中間,起著承上啟下的作用。也因此,業(yè)務層非常easy淪落為僅僅是一個數(shù)據(jù)的中轉(zhuǎn)站,主要就是由于對業(yè)務層詳細的作用和職責沒有理解清楚。
這里用一個樣例來說明業(yè)務層詳細的工作吧。就舉個用戶注冊的樣例。用戶注冊時,界面上須要用戶提供手機號、短信驗證碼、password、確認password。
那么,最簡單的操作就是,帶上這些參數(shù)調(diào)用數(shù)據(jù)層的注冊接口。好了,問題來了,注冊接口并沒有提供確認password的參數(shù)。那好,調(diào)用注冊接口之前先推斷下password和確認password是否一致。不一致則返回錯誤提示給用戶,一致了才調(diào)用注冊接口。好了,第二個問題來了,用戶等網(wǎng)絡請求等了一段時間后。請求結(jié)果返回說手機號少了一位。下一次。又等了一段時間。這次又返回說手機號多了一位。就由于一個小錯誤要讓用戶等那么久。用戶肯定有意見。后臺也有意見,各種非法的請求都發(fā)過來,是嫌server壓力不夠大啊。那好。調(diào)用接口之前對這些參數(shù)做有效性檢查吧。手機號要規(guī)范,短信驗證碼僅僅能為六位數(shù)字,password不能少于六位。最終注冊成功了。第三個問題又來了,注冊接口是沒有返回用戶的accessToken的,僅僅有登錄接口才會返回。
讓用戶手動再登錄一下?這用戶體驗不太好啊。正確的姿勢應該是注冊成功后再自己主動調(diào)用一次登錄接口,假設由于網(wǎng)絡問題第一次登錄失敗,后面還須要再自己主動調(diào)用多一次,假設還是調(diào)用失敗,才讓用戶手動登錄。
上面的樣例中,對參數(shù)的有效性檢查,注冊成功后的自己主動登錄,都屬于業(yè)務邏輯的處理,也就是說都是業(yè)務層的工作。
業(yè)務層交付給展示層的數(shù)據(jù)也是通過接口的方式。只是,和數(shù)據(jù)層交付給業(yè)務層時不同的是:交付給展示層的數(shù)據(jù)應該是通過異步回調(diào)返回的。
由于獲取數(shù)據(jù)是一個比較耗時的任務。通過異步回調(diào)才不會堵塞UI主線程。
展示層
展示層作為數(shù)據(jù)展示者,它僅僅要關(guān)心數(shù)據(jù)怎樣展示就能夠了。只是。數(shù)據(jù)怎樣展示卻不是那么簡單。展示層是三層架構(gòu)中最復雜的一層了。要考慮的東西遠遠多于其它兩層。涉及的東西包含但不限于界面布局、屏幕適配、圖片資源、文本資源、顏色資源等等。在開發(fā)一段時間后。展示層出現(xiàn)代碼混亂是最常見的。
因此。做好展示層。就須要保持高質(zhì)量的代碼。要保持高質(zhì)量代碼。我認為至少應該遵循幾條主要的原則:
- 保持規(guī)范性:定義好開發(fā)規(guī)范,包含書寫規(guī)范、命名規(guī)范、凝視規(guī)范等。并依照規(guī)范嚴格運行;
- 保持單一性:布局就僅僅做布局,內(nèi)容就僅僅做內(nèi)容。各自分離好,每一個方法、每一個類,也僅僅做一件事情;
- 保持簡潔性:保持代碼和結(jié)構(gòu)的簡潔。每一個方法,每一個類,每一個包,每一個文件,都不要塞太多代碼或資源,感覺多了就應該拆分。
所謂無規(guī)矩不成方圓,展示層的設計。要從開發(fā)規(guī)范開始。
一份好的開發(fā)規(guī)范。是保證代碼有較高的可讀性的基礎。
IOS方面,蘋果已經(jīng)有一套Coding Guidelines,主要屬于命名方面的規(guī)范。
當我們制定自己的開發(fā)規(guī)范時,首先就要遵守蘋果的這份規(guī)范。在此基礎上再加上自己的規(guī)范。
Android方面,我也在我的博客中分享過一套(Android技術(shù)積累:開發(fā)規(guī)范),主要分為書寫規(guī)范、命名規(guī)范、凝視規(guī)范三部分。
最重要的不是開發(fā)規(guī)范的制定。而是開發(fā)規(guī)范的運行。假設沒有依照開發(fā)規(guī)范去運行,那開發(fā)規(guī)范就等于形同虛設,那代碼混亂的問題依舊得不到解決。
說到單一性。面向?qū)ο笤O計中。有一個基本原則就是單一職責原則。它規(guī)定一個類應該僅僅有一個發(fā)生變化的原因。保持單一性是減低耦合度的關(guān)鍵標準。其目的就是各方面的解耦。而我這里說的單一性不僅僅是規(guī)定類的單一。也包含界面的單一、方法的單一、資源文件的單一等。
界面的單一,首先是界面的布局和界面的數(shù)據(jù)應該分離。另外。界面數(shù)據(jù)的獲取和展示也應該分離。一句話,保持界面的單一性就是要保持界面上每一個維度都做好分離,從界面的布局。到數(shù)據(jù)的獲取,數(shù)據(jù)的檢查,數(shù)據(jù)的展示。
方法的單一。則表現(xiàn)為一個方法是對一個行為的封裝。行為又能夠拆分為多個步驟,每一個步驟事實上也是更細化的行為。因此。方法嵌套方法是一種常態(tài)。那么,保持方法的單一性,關(guān)鍵不在于怎么定義這種方法的行為,而在于這個行為要怎么拆分成更細的行為。舉個樣例。通常在Activity的onCreate方法,做初始化操作。細分出來就分為了:控件的初始化、邏輯變量的初始化、數(shù)據(jù)的初始化。
數(shù)據(jù)的初始化又能夠再細分:數(shù)據(jù)的獲取、數(shù)據(jù)的展示。每一個細化的行為都應該封裝為一個獨立的方法。這樣,才真正符合方法的單一性。
資源文件的單一,主要是指Android的各類資源文件,包含存放字符串的strings.xml。存放字符串數(shù)組的arrays.xml。存放顏色值的colors.xml。存放尺寸值的dimens.xml,等等。
資源文件的單一,是說全部相關(guān)的資源信息要在資源文件中定義并引用到代碼或布局文件中。而不是在代碼或布局文件中直接定義。這樣做。能夠非常方便地做各種適配和改動,比方支持國際化,比方不同分辨率的屏幕用不同尺寸值。iOS則沒有提供和Android一樣的資源文件分離的機制。但能夠參考Android的做法自己去實現(xiàn)。






