最近在做一功能不大、業務也不復雜的小眾App,以往做App是發現自己從來沒有考慮過一些架構方面的問題,只是按照自己以往的習慣去寫代碼,忽略了App的設計。本次分享主要包含一些開發App的小經驗和技巧,來一次App開發與設計的分享。
先和分享下一下實體類的設計與組織形式
實體類的組織
在做App開發的時候有很多的實體類,項目越復雜實體類就會越多,經過我的一番思考大致這可以將實體分為以下幾大數:
- 面向數據庫的
- 服務端返回的數據實體
- 用於渲染View的實體(使用Databinding)
一般情況下實體類的操作會經過以下步驟:
- App請求服務器獲取數據
- 將數據存入數據庫(可選)
- 渲染頁面展示數據
現在的實體的產生只用在請求服務器數據的時候才需要新建,后續的數據庫、頁面渲染其實是可以使用一套實體:
先不說這樣做的行不行,首先三個地方使用同一實體就會引起字段歧義比如服務器數據有Id、本地數據也有Id,那兩個id字段就有沖突了不得不改字段名。
另一種情況渲染和數據本身並不會一一對應,有時候后端數據給的是一個純數字而前端頁面顯示的是字符串兩個都對應不上,強行放在一起會起來更多的問題。
所為實體類的的正確組織形式應該是:相互隔離、互不干擾:
數據實體的在渲染之前都需要准備好,比如在ViewModel中將int型的數據轉換成文本型的數據然后再使用Databinding+頁面渲染實體來渲染頁面。
優雅的處理網絡數據
現在Android開發使用的網絡庫大部分都是Okhttp + Retrofit
,使用Retrofit網絡交互變的非常簡單一個Service接口就能搞定一切,美茲茲~~,現在大部分后端返回的數據都會是以下形式:
{
"code":0,
"data": {},
"msg": ""
}
雖然不能涵蓋所有,但還是可以非常贊的數據、消息、成功與否啥都有!對於前面主要是關注data
字段,其他msg
、code
等都屬於輔助字段。前端對應的實體對象應該是這樣的(假代碼):
public class ApiResponse<T> {
private int code;
private T data;
private String msg;
}
對應的Service那就得定義成這樣(使用了RxJava):
public intface UserService {
@GET("/xx/{id}")
Single<ApiResponse<UserInfo> getUserInfoById(@Path("id") Long userId);
}
從接口中可以看出來,方法的返回值就包了幾層,如果要拿data
字段需要經過:ApiResponse -> UserInfo
,而且在拿之前還要判斷code
字段:
...
if(ApiResponse.code == 0){
UserInfo info = ApiResponse.getData();
}
...
為了消除這些冗余的代碼可以使用CallAdapter
來使Service方法返回的數據直接就是實體類:
public intface UserService {
@GET("/xx/{id}")
Single<UserInfo> getUserInfoById(@Path("id") Long userId);
}
CallAdapter
的代碼就不貼了,可以自行查找。這樣做帶來的另外一個問題就是業務代碼如何判斷接口是否成功或失敗,前端必需友好的把錯誤提示給用戶而不是一直搞個Loading在那里瞎轉~~。現階段最方便的的錯誤傳遞方式是使用Java異常,前端可以定義業務異常或網絡異常:
public class BizException extends RuntimeException {
...
}
在CallAdapter
中檢查ApiResponse的返回值是否成功:
if(!ApiResponse != 0){
throw new BizExcepiton(ApiResponse);
}
如果后端返回業務異常那前端就對應拋出一個BizExcepiton
,如果是http錯誤如:404、400那可以拋出HttpException
。除了BizExcepiton
和HttpException
外還可使用特定的異常比如后端返回密碼錯誤異常:
public class InvalidPasswordException extends BizException {
...
}
如需特殊處理,也可以滿足要求。
健壯的數據層
現在很多應用都開發使用MVVM開發模式數據層都使用Repository
來表示,面向數據驅動的開發模式,頁面變化都需要隨着數據變更而更新,數據發生變化然后頁面再做出響應。Repository的拆分要細一點,不建議簡單的弄個UserRepository
包含登陸、注冊、更新密碼等等操作,設計Repository
的一些想法:
- 面向接口編程
- 保持單一原則
- 功能邊界要清晰(如:登陸、注冊可以分開)
- 業務邏輯盡可能的少(復雜的業務考慮Presenter)
一個判斷是否是好的設計的辦法可以這樣:一個登陸頁面從Activive/Fragment到ViewModel再到Repository,有沒有多余的代碼。比如上面說的UserRepository
包含登陸、注冊但是在一個登陸頁面就不需要有注冊功能,從登陸頁面上來看注冊的代碼就是多余的(有些App登陸/注冊在一個頁面的~~)。
一個包含登陸、注冊的UserRepository
簡單圖:
另外一點是盡量將repository使用到的一些東西集中管理,可引入一個基礎的repository:
public class SimpleRepository {
protected final <T> T getService(Class<T> clz){
return Services.getService(clz);
}
}
做為SimpleRepository
的子類,就不需要考慮從哪里獲取service的問題。
簡潔的UI層
UI層面可以分為ViewModel和View(Activity/Fragment), View的職責應當只有二點:
- 展示業務數據
- 收集業務數據
例如一些數據的組織、判斷都不應該出現在View中比如:
if (Strings.isNullOrEmpty(phone)) {
...
return;
}
if (Strings.isNullOrEmpty(pwd)) {
...
return;
}
像上面這類的代碼都不應該出現在View中,而在放置在ViewModel里面,View只收集用戶數據傳遞給ViewModel由它來進行數據校驗。再比如像這樣的if/else代碼也應該放置在ViewModel中:
int age = 10;
String desc = "";
if(age < 18){
desc = "青年";
}else if(age < 29){
desc = "中年";
}
如果數據的顯示和數據的收集過多,建議使用Databinding來進行雙向綁定數據。再搭配LiveData
使View作為觀察者實時監聽數據變化:
registerViewModel.getRegistryResult().observe(this, new SimpleObserver<RegistryInfo>(this));
一旦數據發生變化LiveData
就會通知Observer更新,通過DataBinding更新各個頁面數據。
再說ViewModel應該只包含一些簡單的判斷、檢查、打通數據的代碼,如果業務過於復雜可以考慮加Presetner,如果真的超級復雜那可以反思下這個復雜的邏輯應不應該放在前端,能不能放在后端呢?