前两篇文章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./platform/api 参数: 参数名 | 描述 | 类型 |
---|
appKey | ANDROID_KCOUPON | String | method | service.sendSmsCode4Register | String | phoneNum | 手机号码 | String |
输出样例: { 'event': '0', 'msg': 'success' } 注册接口 URL:http://uat.b./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./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./platform/api 其他参数: 参数名 | 描述 | 类型 |
---|
appKey | ANDROID_KCOUPON | String | method | issue.listNewCoupon | String | currentPage | 当前页数 | int | pageSize | 每页显示数量 | int |
输出样例:
Api的实现类则是ApiImpl了,实现类需要封装好请求数据并向服务器发起请求,并将响应结果的数据转为ApiResonse返回。而向服务器发送请求并将响应结果返回的处理则封装到http引擎类去处理。另外,这里引用了gson将json转为对象。ApiImpl的实现代码如下:
至此,接口层的封装就完成了。接下来再往上看看核心层吧。 核心层的逻辑
核心层处于接口层和界面层之间,向下调用Api,向上提供Action,它的核心任务就是处理复杂的业务逻辑。先看看我对Action的定义:
首先,和Api接口对比就会发现,参数并不一致。登录并没有iemi和loginOS的参数,获取券列表的参数里也少了pageSize。这是因为,这几个参数,跟界面其实并没有直接关系。Action只要定义好跟界面相关的就可以了,其他需要的参数,在具体实现时再去获取。 另外,大部分action的处理都是异步的,因此,添加了回调监听器ActionCallbackListener,回调监听器的泛型则是返回的对象数据类型,例如获取券列表,返回的数据类型就是List,没有对象数据时则为Void。回调监听器只定义了成功和失败的方法,如下:
简单的实现代码就是这样,其实,这还有很多地方可以优化,比如,将参数为空的检查、手机号有效性的检查、数字型范围的检查等等,都可以抽成独立的方法,从而减少重复代码的编写。异步任务里的代码也一样,都是可以通过重构优化的。另外,需要扩展时,比如添加缓存,那就在调用Api之前处理。 核心层的逻辑就是这样了。最后就到界面层了。 界面层
在这个Demo里,只有三个页面:登录页、注册页、券列表页。在这里,也会遵循界面篇提到的三个基本原则:规范性、单一性、简洁性。 首先,界面层需要调用核心层的Action,而这会在整个应用级别都用到,因此,Action的实例最好放在Application里。代码如下:
详细代码请查看原文链接 完结
终于写完了,代码也终于放上了github,为了让人更容易理解,因此很多都比较简单,没有再进行扩展。 github地址:https://github.com/keeganlee/kandroid
|