原創文章,轉載請註明:轉載自Keegan小鋼
並標明原文連接:http://keeganlee.me/post/android/20150629
微信訂閱號:keeganlee_me
寫於2015-06-29android
Android項目重構之路:架構篇
Android項目重構之路:界面篇
Android項目重構之路:實現篇git
前兩篇文章Android項目重構之路:架構篇和Android項目重構之路:界面篇已經講了個人項目開始搭建時的架構設計和界面設計,這篇就講講具體怎麼實現的,以實現最小化可用產品(MVP)的目標,用最簡單的方式來搭建架構和實現代碼。
IDE採用Android Studio,Demo實現的功能爲用戶註冊、登陸和展現一個券列表,數據採用咱們現有項目的測試數據,接口也是咱們項目中的測試接口。github
項目搭建
根據架構篇所講的,將項目分爲了四個層級:模型層、接口層、核心層、界面層。四個層級之間的關係以下圖所示:json
實現上,在Android Studio分爲了相應的四個模塊(Module):model、api、core、app。
model爲模型層,api爲接口層,core爲核心層,app爲界面層。
model、api、core這三個模塊的類型爲library,app模塊的類型爲application。
四個模塊之間的依賴設置爲:model沒有任何依賴,接口層依賴了模型層,核心層依賴了模型層和接口層,界面層依賴了核心層和模型層。
項目搭建的步驟以下:api
-
建立新項目,項目名稱爲KAndroid,包名爲com.keegan.kandroid。默認已建立了app模塊,查看下app模塊下的build.gradle,會看到第一行爲:數組
apply plugin: 'com.android.application'
這行代表了app模塊是application類型的。緩存
-
分別新建模塊model、api、core,Module Type都選爲Android Library,在Add an activity to module頁面選擇Add No Activity,這三個模塊作爲庫使用,並不須要界面。建立完以後,查看相應模塊的build.gradle,會看到第一行爲:服務器
apply plugin: 'com.android.library'
-
創建模塊之間的依賴關係。有兩種方法能夠設置:
第一種:經過右鍵模塊,而後Open Module Settings,選擇模塊的Dependencies,點擊左下方的加號,選擇Module dependency,最後選擇要依賴的模塊,下圖爲api模塊添加了model依賴;微信第二種:直接在模塊的build.gradle設置。打開build.gradle,在最後的dependencies一項裏面添加新的一行:compile project(':ModuleName'),好比app模塊添加對model模塊和core模塊依賴以後的dependencies以下:架構
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model') compile project(':core') }
經過上面兩種方式的任意一種,建立了模塊之間的依賴關係以後,每一個模塊的build.gradle的dependencies項的結果將會以下:
model:dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' }
api:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model') }
core:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model') compile project(':api') }
app:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model') compile project(':core') }
建立業務對象模型
業務對象模型統一存放於model模塊,是對業務數據的封裝,大部分都是從接口傳過來的對象,所以,其屬性也與接口傳回的對象屬性相一致。在這個Demo裏,只有一個業務對象模型,封裝了券的基本信息,如下是該實體類的代碼:
/**
* 券的業務模型類,封裝了券的基本信息。 * 券分爲了三種類型:現金券、抵扣券、折扣券。 * 現金券是擁有固定面值的券,有固定的售價; * 抵扣券是知足必定金額後能夠抵扣的券,好比滿100減10元; * 折扣券是能夠打折的券。 * * @version 1.0 建立時間:15/6/21 */ public class CouponBO implements Serializable { private static final long serialVersionUID = -8022957276104379230L; private int id; // 券id private String name; // 券名稱 private String introduce; // 券簡介 private int modelType; // 券類型,1爲現金券,2爲抵扣券,3爲折扣券 private double faceValue; // 現金券的面值 private double estimateAmount; // 現金券的售價 private double debitAmount; // 抵扣券的抵扣金額 private double discount; // 折扣券的折扣率(0-100) private double miniAmount; // 抵扣券和折扣券的最小使用金額 // TODO 全部屬性的getter和setter }
接口層的封裝
在這個Demo裏,提供了4個接口:一個發送驗證碼的接口、一個註冊接口、一個登陸接口、一個獲取券列表的接口。這4個接口具體以下:
-
發送驗證碼接口
URL:http://uat.b.quancome.com/platform/api
參數:參數名 描述 類型 appKey ANDROID_KCOUPON String method service.sendSmsCode4Register String phoneNum 手機號碼 String 輸出樣例:
{ "event": "0", "msg": "success" }
-
註冊接口
URL:http://uat.b.quancome.com/platform/api
參數:參數名 描述 類型 appKey ANDROID_KCOUPON String method customer.registerByPhone String phoneNum 手機號碼 String code 驗證碼 String password MD5加密密碼 String 輸出樣例:
{ "event": "0", "msg": "success" }
-
登陸接口
URL:http://uat.b.quancome.com/platform/api
其餘參數:參數名 描述 類型 appKey ANDROID_KCOUPON String method customer.loginByApp String loginName 登陸名(手機號) String password MD5加密密碼 String imei 手機imei串號 String loginOS 系統,android爲1 int 輸出樣例:
{ "event": "0", "msg": "success" }
-
券列表
URL:http://uat.b.quancome.com/platform/api
其餘參數:參數名 描述 類型 appKey ANDROID_KCOUPON String method issue.listNewCoupon String currentPage 當前頁數 int pageSize 每頁顯示數量 int 輸出樣例:
{ "event": "0", "msg": "success", "maxCount": 125, "maxPage": 7, "currentPage": 1, "pageSize": 20, "objList":[ {"id": 1, "name": "測試現金券", "modelType": 1, ...}, {...}, ... ]}
在架構篇已經講過,接口返回的json數據有三種固定結構:
{"event": "0", "msg": "success"} {"event": "0", "msg": "success", "obj":{...}} {"event": "0", "msg": "success", "objList":[{...}, {...}], "currentPage": 1, "pageSize": 20, "maxCount": 2, "maxPage": 1}
所以能夠封裝成實體類,代碼以下:
public class ApiResponse<T> { private String event; // 返回碼,0爲成功 private String msg; // 返回信息 private T obj; // 單個對象 private T objList; // 數組對象 private int currentPage; // 當前頁數 private int pageSize; // 每頁顯示數量 private int maxCount; // 總條數 private int maxPage; // 總頁數 // 構造函數,初始化code和msg public ApiResponse(String event, String msg) { this.event = event; this.msg = msg; } // 判斷結果是否成功 public boolean isSuccess() { return event.equals("0"); } // TODO 全部屬性的getter和setter }
上面4個接口,URL和appKey都是同樣的,用來區別不一樣接口的則是method字段,所以,URL和appKey能夠統必定義,method則根據不一樣接口定義不一樣常量。而除去appKey和method,剩下的參數纔是每一個接口須要定義的參數。所以,對上面4個接口的定義以下:
public interface Api { // 發送驗證碼 public final static String SEND_SMS_CODE = "service.sendSmsCode4Register"; // 註冊 public final static String REGISTER = "customer.registerByPhone"; // 登陸 public final static String LOGIN = "customer.loginByApp"; // 券列表 public final static String LIST_COUPON = "issue.listNewCoupon"; /** * 發送驗證碼 * * @param phoneNum 手機號碼 * @return 成功時返回:{ "event": "0", "msg":"success" } */ public ApiResponse<Void> sendSmsCode4Register(String phoneNum); /** * 註冊 * * @param phoneNum 手機號碼 * @param code 驗證碼 * @param password MD5加密的密碼 * @return 成功時返回:{ "event": "0", "msg":"success" } */ public ApiResponse<Void> registerByPhone(String phoneNum, String code, String password); /** * 登陸 * * @param loginName 登陸名(手機號) * @param password MD5加密的密碼 * @param imei 手機IMEI串號 * @param loginOS Android爲1 * @return 成功時返回:{ "event": "0", "msg":"success" } */ public ApiResponse<Void> loginByApp(String loginName, String password, String imei, int loginOS); /** * 券列表 * * @param currentPage 當前頁數 * @param pageSize 每頁顯示數量 * @return 成功時返回:{ "event": "0", "msg":"success", "objList":[...] } */ public ApiResponse<List<CouponBO>> listNewCoupon(int currentPage, int pageSize); }
Api的實現類則是ApiImpl了,實現類須要封裝好請求數據並向服務器發起請求,並將響應結果的數據轉爲ApiResonse返回。而向服務器發送請求並將響應結果返回的處理則封裝到http引擎類去處理。另外,這裏引用了gson將json轉爲對象。ApiImpl的實現代碼以下:
public class ApiImpl implements Api { private final static String APP_KEY = "ANDROID_KCOUPON"; private final static String TIME_OUT_EVENT = "CONNECT_TIME_OUT"; private final static String TIME_OUT_EVENT_MSG = "鏈接服務器失敗"; // http引擎 private HttpEngine httpEngine; public ApiImpl() { httpEngine = HttpEngine.getInstance(); } @Override public ApiResponse<Void> sendSmsCode4Register(String phoneNum) { Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put("appKey", APP_KEY); paramMap.put("method", SEND_SMS_CODE); paramMap.put("phoneNum", phoneNum); Type type = new TypeToken<ApiResponse<Void>>(){}.getType(); try { return httpEngine.postHandle(paramMap, type); } catch (IOException e) { return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG); } } @Override public ApiResponse<Void> registerByPhone(String phoneNum, String code, String password) { Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put("appKey", APP_KEY); paramMap.put("method", REGISTER); paramMap.put("phoneNum", phoneNum); paramMap.put("code", code); paramMap.put("password", EncryptUtil.makeMD5(password)); Type type = new TypeToken<ApiResponse<List<CouponBO>>>(){}.getType(); try { return httpEngine.postHandle(paramMap, type); } catch (IOException e) { return