在上次,我思考了关于结构方面的Android项目重构之路:架构篇 读后思考,那么这次,我在读了Android项目重构之路:实现篇之后,也有了新的收获
由于现在所在的公司比较小,android studio是没有使用的,不过在看着那篇文章之后,对于其中的模块原理有了一个基础的了解,原本对于网上分享的一些在android studio项目中界面特效的代码我是无法一下子理解的,现在,我理解了,一边实践一边学习的确是一个很有帮助的方式
还是以上次的那张结构图来展开总结
界面层(app)、核心层(core)、接口层(api)、模型层(model)
界面层依赖核心层与模型层,核心层依赖接口层与模型层,接口层依赖模型层
先遵循文章的顺序来进行深入思考
一、模型层的解读与思考
首先是模型层(model),就文章中的模型层来说,由于为了看得人更容易的理解,所以逻辑比较简单,只是一个拥有一些数据的奖券对象,并没有什么需要深究的地方。
有一点我不是很赞同的是bean对象他实现了Serializable而不是Parcelable,因为在我们程序实际过程中一般都需要
进行数据恢复,在程序进入后台时很有可能会被回收,这时候需要恢复,而android针对Serializable恢复起来很麻烦,而且对程序的负担比Parcelable大
但是,在我们实际的项目中,可能情况就会不这么简单,因为很多时候这个对象本身可能也会包含一些逻辑功能。
比如,如果这是一个用户对象bean类,如果围绕这个用户有两个需求
1、获取头像
2、获取他有哪些优惠券
那么这两个方法调用的地方应该如何分配,这里有两种思维的思路:
①、如果以面向对象的思维,头像是用户自己的,应该可以直接从bean获取,优惠券也是,这个用户对象里面应该就要有一个集合存放他有的优惠券,就像一个人,他口袋里有很多优惠券,理应可以从口袋里立刻取出来,并且一直都在
②、这个用户对象就只是一个纯粹的数据保存的地方,它是一个残缺的模拟的对象,他不具备获取自己头像bitmap的功能,因为实际生活中不可能存在一个人要去获取自己长相视图的活动,除非是生成照片交给别人或自己,那么这个时候按照这种写法我们就会需要有一个专门处理获取头像的管理中心,然后要有一个获取用户优惠券的管理中心,这对于调用的人来说是否方便,能否很好的理解?
而我在上次读了结构篇之后得出的一个结论是对于功能区分一下,获取头像可以放在对象bean里面,而获取优惠券这个行为则放在一个管理中心之中
在今天经过反复的纠结与思考之后我得出的结论是:
那么这种区分规则其实应该并没有什么关键点,这种关键点就是自己给自己代码的风格的一种界定,而这种界定的关键就是界面调用的方便+容易理解+类的读取方便+类的修改方便
所以我给自己定下的规则是尽量保持bean对象内部不包含其他对象,不同的对象应由不同的核心层来管理,当一定要包含其他类的一些数据时,只保存对应的id,比如这个用户所属的公司,只保存该公司的id,而不保存该公司的对象或者公司名等数据,在网络下载下来的数据包含两种数据时,也在解析时分离保存,交给数据管理中心
所以对于上面那两种数据的处理是,头像可以通过User对象直接获取,而他所有的优惠券则通过数据管理中心来获取
二、接口层的解读与思考
public class ApiResponse {
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
}
这一段代码我觉得很有意思,之前对于接口层我了解的不够清晰,因为以我之前的代码经验来说,从来没有写过这样的接口,现在觉得颇有所得(结合原文接下来的内容之后),只是这种写法可能还需要实际磨练一下方可熟练
这种写法将返回的结果封装在了一个接口层定义的对象中,包括网络的访问状态和信息也封装在内。
再往下看接口的定义:
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 sendSmsCode4Register(String phoneNum);
/**
* 注册
*
* @param phoneNum 手机号码
* @param code 验证码
* @param password MD5加密的密码
* @return 成功时返回:{ "event": "0", "msg":"success" }
*/
public ApiResponse 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 loginByApp(String loginName, String password, String imei, int loginOS);
/**
* 券列表
*
* @param currentPage 当前页数
* @param pageSize 每页显示数量
* @return 成功时返回:{ "event": "0", "msg":"success", "objList":[...] }
*/
public ApiResponse> listNewCoupon(int currentPage, int pageSize);
}
接口的定义就涉及到什么时候需要定义接口,针对哪些方法、功能应该定义接口,关于这一点,我以前是很懵懂的,但是,现在经过对代码的反复查看和思考之后我得出了结论
层与层之间,针对层与层之间调用的方法,需要定义接口,而弄清楚并写下接口的前提,就是将项目整体的结构定好,然后在定好的结构之内,关键的功能调用处写下接口
继续往下看,则是Api的实现类:ApiImpl ,这个类里面我只拿一个方法来进行分析,因为其实每一个方法都是一样的模式
public ApiResponse sendSmsCode4Register(String phoneNum) {
Map paramMap = new HashMap();
paramMap.put("appKey", APP_KEY);
paramMap.put("method", SEND_SMS_CODE);
paramMap.put("phoneNum", phoneNum);
Type type = new TypeToken>(){}.getType();
try {
return httpEngine.postHandle(paramMap, type);
} catch (IOException e) {
return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
}
}
接下来去看下载器的实现部分,因为我可以看到在这里当下载报错时,唯一的反馈是“服务器连接失败”,错误码也只有time_out,这一点来说应该是由于作者希望以最简单的实现,但是,我在思考时,则需要联系实际来进行思考
从用户的角度来说,当数据加载出错时,其实并不需要知道是什么原因,或者说,最终我们应该展示给用户的,应该只有两种情况,一种是没有网络,另一种是网络信号差,请刷新,对于用户来说我们的代码不应该由于服务器或客户端的BUG产生无法使用的情况。这是我们所希望的情况,因为我们的代码应该不会出任何问题
从我们开发者的角度来说,我们会希望知道,出错时出了什么错,这时候我们会希望知道具体是什么原因引起的,对于我们调试来说比较有帮助,对于我们开发者来说,错误有几种:1、无网络。2、网络信号差下载失败(连接超时)。3、解析时出现问题(这里包括不是我们想要的内容(cmcc)、本地与服务器关键字对接问题(某个关键字没有或标签错误)).4、服务器访问不正常(404等)
那么针对我之前在参考下载器的没有在线程内实现时总结到的最小功能独立原则,博主的代码应该将网络连接与解析分离开来,而不是都写在
httpEngine.postHandle(paramMap, type)
这个方法中,然后在
sendSmsCode4Register(String phoneNum)
这个方法中添加各种在下载和解析时报错的处理,然后将简化到是没有网络还是一切需要用户刷新的处理结果返回
然后,在查看下载器时,我又发现一点,那就是所有的数据下载都是使用post方式来请求,而我在实际工作过程中,则会出现有的接口需要post方式,有的需要get方式,就我以前的认知而言,方式的选择取决于是否需要数据保密,但是,又有很多请求时,get方式内也会出现用户的id,这种时候为什么是使用get呢?于是我针对post和get方式进行一番理解之后,我总结出一个结果
get方式用于固定页面的获取,不需要前置数据操作条件即可访问,例如我应该打开浏览器在没有进入任何网站的时候可以直接通过网址访问我的博客主页,或者我的某一篇博客,这种时候如果希望使用post方式访问将无法做到,因为我们一开始是没有前置数据的,以及不需要数据保密并且后缀较少的时候,那么这种情况使用get方式
post方式用于访问需要在有前置数据的支持下才可访问,以及需要保密上传的数据的时候,这种方式在网页端通常只能在某个页面加载完毕,在数据准备好之后才能访问,而在手机端,则需要在本地有所需要的必要数据时才能使用
三、核心层的解读与思考
核心层的代码主要就是将各个工具组合起来,然后就像我之前发现的那样,在一个层的主要调用方法都定义接口
public interface AppAction {
// 发送手机验证码
public void sendSmsCode(String phoneNum, ActionCallbackListener listener);
// 注册
public void register(String phoneNum, String code, String password, ActionCallbackListener listener);
// 登录
public void login(String loginName, String password, ActionCallbackListener listener);
// 按分页获取券列表
public void listCoupon(int currentPage, ActionCallbackListener> listener);
}
在很多安卓的文章中,可以发现需要线程中执行的代码基本都是使用AsyncTask来完成的,而我则一直使用的new Thread然后配合Handler.post来达到类似的效果,官方的推荐方法必定有什么原因,看来我得找时间熟悉一下AsyncTask。
四、界面层的解读与思考
这一部分的代码不多,可以看到两点用到了继承:Activity和Adapter,这种方式再不同的项目之中则是灵活运用,不过可以发现,在Adapter部分,他的数据是由Adapter来进行保存于管理的,这固然使得下载数据部分需要做的事情变少了,不过也存在一点问题:
1、不利于数据恢复,在低端手机界面不显示时很容易被数据回收,这个时候博主的代码数据需要添加数据恢复的方法可能对于界面来说管理比较杂,虽然说不是无法实现,但总比有一个数据管理中心复杂
2、如果是出现在多个界面可能会有部分数据是一样的,并且数据的状态随着操作会改变时,由界面这种方式管理的数据将无法做到同步
还有一部分则是博主提的一些命名规则等规范,这个在不同公司规范都是不同的,不过博主的一些明明方式的确具有一定参考价值
那么到这里,思考就基本结束,总的来说收获很大,博主的代码的思维对我的启发很大,我以后还需要继续不断学习,提升编程思维以及技术