关于简物的项目分析

为什么拿简物做项目分析?

简物不是我在某个公司完成的产品,也并非给某个公司外包的产品,所以把这个项目拿出来写一系列关于我在Android开发上的一些个人见解和项目分析,应该能更好的体现简物在Android开发上所涉及的知识点,如有不对的地方,还请标明指教
因 简物 尚未上线应用市场,为避免搜索引擎抓取出现这个名词,以下将名词替换为Jianwoo
注:Jianwoo的Android端开发 + 服务端(php)开发 + 产品功能设计 + UI界面设计均由个人完成,产品功能上做了很多竞品分析,吸取了一些我个人认为较好的app的一些功能、以及排版和设计,最后诞生了现在这个版本的简物,图标取自于阿里图标库,服务器租赁阿里云,代码托管于Bitbucket

About Jianwoo

简物是一个以日式简约风、米白色系商品为主的纯电商项目,包含的功能模块有商品列表、商品详情、主题、分类、购物车、下单、支付、个人信息、收货地址、订单列表等,以及高仿Pinterest交互等UI交互模块
因为时间有限,我将会挑出一下我认为优先的功能点做分析,其它功能不作详细介绍,接下来会涉及到的分析模块会包含以下:

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
关于项目结构和分层内容有太多,今天就就写到这里,因为后面还有很多东西没开始…

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容