为什么拿简物做项目分析?
简物不是我在某个公司完成的产品,也并非给某个公司外包的产品,所以把这个项目拿出来写一系列关于我在Android开发上的一些个人见解和项目分析,应该能更好的体现简物在Android开发上所涉及的知识点,如有不对的地方,还请标明指教
因 简物 尚未上线应用市场,为避免搜索引擎抓取出现这个名词,以下将名词替换为Jianwoo
注:Jianwoo的Android端开发 + 服务端(php)开发 + 产品功能设计 + UI界面设计均由个人完成,产品功能上做了很多竞品分析,吸取了一些我个人认为较好的app的一些功能、以及排版和设计,最后诞生了现在这个版本的简物,图标取自于阿里图标库,服务器租赁阿里云,代码托管于Bitbucket
About Jianwoo
简物是一个以日式简约风、米白色系商品为主的纯电商项目,包含的功能模块有商品列表、商品详情、主题、分类、购物车、下单、支付、个人信息、收货地址、订单列表等,以及高仿Pinterest交互等UI交互模块
因为时间有限,我将会挑出一下我认为优先的功能点做分析,其它功能不作详细介绍,接下来会涉及到的分析模块会包含以下:
- 1.0、Jianwoo的项目框架、分层设计
- 2.1、Jianwoo中的设计模式(1) — 单例模式
- 2.2、Jianwoo中的设计模式(2) — 观察者模式
- 2.3、Jianwoo中的设计模式(3) — Builder模式
- 2.4、Jianwoo中的设计模式(4) — 工厂方法模式
- 2.5、Jianwoo中的设计模式(5) — 模板方法模式
- 2.6、Jianwoo中的设计模式(6) — 原型模式
- 2.7、Jianwoo中的设计模式(7) — 责任链模式
- 3.0、高仿Pinterest交互的实现思路
- 4.0、Jianwoo中的性能优化 — 如何优化到打开170个界面不卡顿
- 5.0、Jianwoo中随机商品的实现逻辑
- 6.1、Jianwoo中的小技巧 — 用位运算符给一个变量多种状态
- 7.2、Jianwoo中的小技巧 — 不直接使用原生常用控件的好处
- 8.0、善于利用Java多态
- 9.0、实现可触摸拖动的Dialog
1、Jianwoo的项目结构、分层设计
我没用“架构”这个词,是因为在很多人眼里,搭架构是一件需要高技巧,有深厚代码功底的人才能做的事情,学无止境,我不想预支这个词,但是一个项目的结构和、分层设计是一个项目的核心,一个好的项目框架能为后期的扩展做非常大的贡献,当然并没有完全适合某一个项目的项目结构,在分析这个问题的时候要做的应该是择优改良,学习好的,抛弃不好的,这才是看别人框架该有的角度,而不是一味的去评论某一个框架的好坏
很多人在搭建项目的时候可能会知道用MVC、MVP等模式去搭建,这大概是现在程序猿使用最多的搭建模式,Jainwoo也是基于MVC模式搭建的框架,但我更想说的是分层设计(当然MVC本身就是分层设计),在我入门php开发的时候,我接触到了一个非常棒的代码框架Laravel,不得不说Laravel中的代码分层做的非常棒,为何这么说,一个好的代码分层结构,除了能让项目快速的扩展,另外就是能让别人快速的上手你的项目,在我学习使用Laravel时,里面的路由、查询构造器、Controller、App、Model模块,能让你非常清晰的了解每个层次所要做的事情,也就是作为一个在出现问题时也能快速定位问题所在,这与我在做Android开发所搭建的项目结构完全是一样的思路,分层明确可以为项目日后扩展做好很强硬的铺垫
那么Jianwoo中的项目结构和分层是怎样的呢?
Jianwoo中的项目结构分为 app(Activity、Fragment、Dialog)、category、adapter、listener、(model、impl、view)、http、db、definition、util等
各个层所对应的功能:
app:Activity、Fragment、Dialog等视图组件的入口,对应生命周期方法的重写,系统回调以及EventBus/广播通知的接收层
category:视图层UI的封装层,model接口获取数据的调用/回调层,UI更新和业务逻辑都在这个层下处理
listener:视图与人交互的监听层,onclick、onLongClick、onCheckedChanged等等,也就是所有涉及到人机交互的操作,均可以快速在listener层中定位
model:网络请求的接口模型,在业务逻辑层只调接口,不关心网络请求的具体实现
impl:model层接口的实现类,也是真正处理网络请求的地方,通过callbackview回调数据给category,实现category(view) <一>model(impl)的双向绑定,也是单一观察者的观察者模式的模型,model层数据变更可自动通过view回调给category,实现界面数据与回调动态更新;为什么说category层只用model接口去走网络请求,试想一下,我现在用的是OkHttp请求网络数据,假设我用了一个类去写网络请求(或者网络请求代码直接写在了Activity),那如果项目中替换了网络请求库那是不是意味着我的业务逻辑层就需要涉及到大量的改动,如果我业务逻辑层请求网络数据走的是接口,通过一个接口callback来回调数据,那即使替换了请求库,我也无需改动绑定UI更新界面的代码,业务逻辑部分我可以不动,我只需要替换对应接口的实现类即可,实现最低程度的修改业务逻辑,这是不是很符合设计模式中所提倡的开闭原则?另外我用model和impl以及callbackview去封装网络请求的整个流程,我可以方便的复用请求代码到任意地方,只要业务功能一致我不需要写第二份代码,实现高度复用
view:model接口实现类请求的回调View层,由category层实现,model.impl/db层请求成功数据成功后通过callbackview回调数据到category更新UI界面,刚刚说了,这也是属于观察者模式中的单一观察者的模型,也是MVC中M一>V的过程
http:Http请求的封装类,这封装的是OkHttp请求,内部请求为AsynTask操作,主线程回调结果
db:本地缓存/数据库的封装层,网络请求数据、运行时序列化数据存储,一般用于无网络时数据存取和内存优化时释放(数据存储本地)不可见Activity/Fragment资源,再次可见时本地取数据更新还原界面时操作
definition:常量包
util:工具类
写了这么多层次的对应职责,我画了一张图来说明各个层次之间的相互关系,可能更好理解
项目结构分层之间的关系
代码分层有什么好处?
1/ 分层能明确开发中各个层次的职责,每个层次做对应职责的事情
2/ 分层结合模板模式抽象开发中的主要流程,把核心方法交给子类实现,能统一开发中的基本流程规范
3/ 分层能快速定位开发中出错的位置,如网络请求数据不对,一定在model/impl层;控件点击无响应可定位到listener层;UI数据更新有误,可在category层进行查错;系统类回调以及事件通知不正确在app层排除。我曾经在参与公司项目中开发时就遇到过各种乱七八糟的bug,根本无法查错,甚至有同事为了一时快速解决一些bug(如空指针),直接在代码调用前判断if(null != object),而不进行源头错误追踪,导致bug隐式藏在某个位置随着迭代更新而越来越难处理
4/ 分层能对一类具有共同特性的功能进行统一封装,实现一劳永逸,比如我们举个listener层的简单的例子
通过分层的统一封装一次性解决项目中所有view 的double click bug
我们在做完功能,交付产品时,测试总是会有各种神奇的手段让你出现bug,比如快速点击你写的按钮,快速重复点击提交订单,让你头疼不已。甚至我见过很多程序猿直言测试“这种操作的用户是不可能有的,那种是傻X用户吧点那么快,要这样改每个地方都要在点完后标记不可点,数据回调后设置回可点,代码会很累赘,开发不可能做这种处理”,balabala的怼回产品和测试
整个app的double click问题真的是很不好处理吗?如果你善于利用抽象和封装,利用Java的多态性,你会发现,这种问题,根本不是问题
下面我们以实际问题来写解决方案,未解决问题前,我的代码是这样的(简化部分逻辑)
LoginActivity:
public class LoginActivity extends BaseActivity {
@Override
protected void setContentView() {
setContentView(R.layout.activity_login);
}
@Override
protected void finishActivity() {
getCategory().clickBack();
}
@Override
protected void initCategory() {
category = new LoginCategory(this);
}
@Override
public LoginCategory getCategory() {
return (LoginCategory) super.getCategory();
}
}
LoginCategory:
public class LoginCategory extends Category {
....
@Bind(R.id.login)
TextView mLogin;
@Bind(R.id.register)
TextView mRegister;
@Bind(R.id.mobile)
QHEditText mMobile;
@Bind(R.id.password)
QHEditText mPassword;
public LoginCategory(BaseActivity activity) {
super(activity);
}
@Override
protected void prepare() {
initUserLoginByMobileModel();
}
@Override
protected void findCategoryViews() {
ButterKnife.bind(this, getActivity());
}
@Override
protected void initThisListener() {
listener = new LoginListener(this);
}
/**
* 给View设置监听
*/
@Override
protected void setThisListener() {
mLogin.setOnClickListener(listener);
mRegister.setOnClickListener(listener);
}
@Override
protected void init() {
initHandler();
initTimer();
...
}
@Override
protected void initViews() {
titleLayout.whiteColor();
...
}
...
}
LoginListener:
public class LoginListener extends Listener {
public LoginListener(Category category) {
super(category);
}
/**
* 重写Listener中onClickView方法
* 这里并没有直接重写onClick方法,是因为父类将onClick方法实现了,并且调用了一个空方法onClickView
*/
@Override
public void onClickView(View v) {
switch (v.getId()){
case R.id.back:
...
break;
case R.id.login:
...
break;
case R.id.register:
...
break;
}
}
private void clickRegister(){
Intent intent = new Intent(getActivity(),
ClassFactory.getClassName(ClassPath.REGISTER));
BaseUtils.jumpToNewActivity(getActivity(), intent);
}
@Override
public LoginCategory getCategory() {
return (LoginCategory) super.getCategory();
}
}
我们来看看父类Listener中干了什么
实现了View.OnClickListener,并且实现了onClick(View v)方法
public abstract class Listener implements
View.OnClickListener,
...
{
protected Category category;
protected BaseActivity activity;
public Listener(Category category){
this.category = category;
this.activity = category.getActivity();
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.back){
BaseUtils.finishActivity(getActivity());
}
onClickView(v);
}
public void onClickView(View v) {
}
大致就是这个流程,我们主要关心Listener这个类,因为这个类接管了App中所有控件的监听事件,包括onClick事件,也就是所有的点击事件都要经过这个Listener“边境”进行检查,那这就好办了,既然都要经过这里检查,那我们给它设置关卡,拒绝不合法公民走私不就好了
现在Listener是直接重写了onClick方法,在里面将back事件统一处理,并且通过一个空方法留给子类们重写,我们要处理的就是onClick方法,如果我们给两次点击事件添加个时间判断,两次点击的时间大于我们设定的最小间隔时间,就允许它点击,小于我们就认为是快速连按,那我们就不允许它点击
开始处理,我们新建一个抽象类
OnNoneDoubleClickListener
public abstract class OnNoneDoubleClickListener implements View.OnClickListener {
/**
* 间隔最短的点击时间,小于这个时间认为是快速点击
*/
public static final int MIN_CLICK_TIME = 500;
long mLastClickTime = System.currentTimeMillis();
public abstract void onClickView(View v);
/**
* 重写onClick方法,判断点击时差
* @param v
*/
@Override
public void onClick(View v) {
long currentTime = System.currentTimeMillis();
/**
* 如果点击时间大于最小间隔时间,允许将时间分发给onClickView方法
*/
if(currentTime - mLastClickTime > MIN_CLICK_TIME){
mLastClickTime = currentTime;
onClickBack(v);
onClickView(v);
}
}
/**
* 别忘了我们原来的onClick方法还有一个处理全局back事件的代码块
* 这里我们用抽象类走流程,在Listener中实现这个方法
*/
protected void onClickBack(View v){}
/**
* 别忘了我们原来的onClick方法还有一个处理全局back事件的代码块
* 这里我们用抽象类走流程,在Listener中实现这个方法
*/
public abstract void onClickBack();
}
那么Listener就变成了
public abstract class Listener extends OnNoneDoubleClickListener
...
{
protected Category category;
protected BaseActivity activity;
public Listener(Category category){
this.category = category;
this.activity = category.getActivity();
}
@Override
protected void onClickBack(View v) {
if(v.getId() == R.id.back){
BaseUtils.finishActivity(getActivity());
}
}
@Override
public void onClickView(View v) {
}
看到没,我们不用动任何一个监听逻辑的代码,就解决了全局double click的bug,再也不怕测试疯狂的点啦
这就是分层和Java多态的妙处,试想一下,如果我没有通过分层来处理人机交互监听,那项目中得有多少代码需要我处理这个bug
关于项目结构和分层内容有太多,今天就就写到这里,因为后面还有很多东西没开始…