Android项目重构之路:实现篇
Android项目重构之路:实现篇
原创文章,转载请注明:转载自Keegan小钢
并标明原文链接:http://keeganlee.me/post/android/20150629
微信订阅号:keeganlee_me
写于2015-06-29
Android项目重构之路:架构篇
Android项目重构之路:界面篇
Android项目重构之路:实现篇
前两篇文章Android项目重构之路:架构篇和Android项目重构之路:界面篇已经讲了我的项目开始搭建时的架构设计和界面设计,这篇就讲讲具体怎么实现的,以实现最小化可用产品(MVP)的目标,用最简单的方式来搭建架构和实现代码。
IDE采用Android Studio,Demo实现的功能为用户注册、登录和展示一个券列表,数据采用我们现有项目的测试数据,接口也是我们项目中的测试接口。
项目搭建
根据架构篇所讲的,将项目分为了四个层级:模型层、接口层、核心层、界面层。四个层级之间的关系如下图所示:
实现上,在Android Studio分为了相应的四个模块(Module):model、api、core、app。
model为模型层,api为接口层,core为核心层,app为界面层。
model、api、core这三个模块的类型为library,app模块的类型为application。
四个模块之间的依赖设置为:model没有任何依赖,接口层依赖了模型层,核心层依赖了模型层和接口层,界面层依赖了核心层和模型层。
项目搭建的步骤如下:
-
创建新项目,项目名称为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