开发一个简易的干货客户端

前言

前一段时间在微博上看到了一个面试题,要求一定时间内开发一个简易的 Gank.io 客户端,虽说笔者并无求职意向,但作为练手感觉也很不错,就尝试了一下。

GitHub Repo: unixzii / Android-Proficiency-Exercise
建议大家对照代码阅读本文!!

App 运行截图如下:


题目要求:

  • 可以调用 API 获取数据
  • 异步加载图片并缓存
  • 下拉刷新,上拉加载更多
  • 可以将数据缓存到数据库以供离线浏览

其实乍一看是一个非常简单的小项目,我在 5 个小时内快速写出了一个版本,并向原 Repo 发起了 PR,但是这一版的代码仅仅是完成功能,也就是实现题目要求的界面和功能,但是性能和代码并不是最优状态。今天又花了近 4 个小时进行了一次重构,目前来说是一个比较完美的状态了。

开发模式

笔者是 iOS 开发者,Android 仅仅是作为业余爱好来研究,但应用开发归根到底都是相通的,唯一不同的就是 API 及平台差异。

这个 app 我采用了很常见的 MVP 开发模式。将应用分为了三大模块,分别是:

  • Main(包括 Toolbar、TabLayout、Fragment Container),这部分负责协调初始化子模块,并加载标签。
  • Entity(Main 选中标签所对应的内容界面),这部分被 Main 所复用,用于加载展示数据。
  • WebView,这部分我很简单地实现了一下,主要是用来展示文章的。

对于每一个模块,我都写了一个 Contract 契约类,它用来规定 Presenter 与 View 之间的交互,契约类中包括 PresenterView 两个内部接口,这样就非常有利于模块的多人协作开发,各部分相互独立,各部分开发时不需了解其它部分的实现,只需调用接口中方法即可。

View & Presenter

我们来看 Entity 模块的 Contract 类:

public interface EntityContract {

    interface View {

        int STATE_LOADING_IDLE = 0;
        int STATE_LOADING_REFRESHING = 1;
        int STATE_LOADING_RESERVING = 2;

        void setLoading(int state);

        void addEntities(List<Entity> entities);

        void clearEntities();

        void showNetworkError();

        void runOnUiThread(Runnable runnable);

    }

    abstract class Presenter extends BasePresenter<View> {

        abstract void setCategory(String categoryName);

        abstract String getCategory();

        abstract void refresh();

        abstract void reserve();

    }

}

View 和 Presenter 所提供的功能一目了然,这里的 BasePresenter 是一个范型抽象类,其中实现了 attaching 和 detaching 时的相关处理,并提供了获取 View 的便利方法。

这里的 View 我才用 Fragment 来实现,列表选用了 RecyclerView,事实上列表控件也属于一个小的 MVC 组件,Adapter 作为 Model,那么我们如何细化这部分的实现呢?整个 app 的 Model 层我写的比较简单,基本就是 Bean 类,完全可以直接交给 View 层作为一个 ViewModel,而 View 层的接口也十分简明,除了一些状态接口外,剩下的就是操作这些 ViewModel 的接口,我们可以向 View 中添加 ViewModels 也可以清空。对于已经添加进视图的 ViewModels,View 就负责展示这些数据即可,无需与 Presenter 进行再次交互。

那么 Presenter 的职责也很分明,就是负责加载数据,处理数据,做缓存处理等工作的。创建 Presenter 的时候我们无须操心 Context 的问题,我创建了一个 Application 单例,Presenter 没有要进行 UI 操作的必要,因此使用 Application Context 就可以满足数据库、磁盘、网络等操作。创建 Presenter 的时候我们可以配置好所需的 Retrofit、OkHttpClient、数据库助手类等对象,当然这里我们也可以使用单例,然后用 Dagger2 注入进去。

然后我们来看看缓存的处理:
在 Presenter 被 attach 到 View 上时,我们进行缓存的加载,因为这时 View 一定是空状态,所以我们执行的加载逻辑:


Presenter 缓存是一个存在于 Presenter 类内部的数组列表,是一级缓存,Fragment destroyView 后,Presenter 类并不会销毁,因此在 Fragment 再次 createView 的时候,我们可以直接拿出其中缓存的数据供 View 显示。

如果 Presenter 中没有缓存,则尝试从数据库读取缓存数据(作为二级缓存),如果数据库也不存在缓存数据才进行网络请求。

网络 & 缓存

网络请求方面采用了 Retrofit + RxJava + Gson,这方面的文章其实很多,本文不想多赘述。这里谈谈缓存,现在的缓存无非分为两大种:

  • 数据库缓存
  • Response 缓存

数据库缓存稍微麻烦一些,但是好处也是显而易见的,数据读写更加灵活,可控性更强;Response 缓存就是将服务器的响应(JSON、XML、BLOB、Protobuf 等)用 Hash 算法进行存储,下次请求相同的 API 时就能直接拿到,好处就是实现简单,加载逻辑不需要做区分处理,每次都按照网络请求来处理即可,但是缺点就是不够灵活,可扩展性差。

我采用了数据库存储,简单地封装了 Android 中原生的数据库操作类,然后写了一个 DAO 来隐藏 SQL 实现细节,因为有些干货数据中含有图片数组,因此一张表肯定是不够的,我们需要构建第二张表来保存图片 URL 数据。由于我们不会直接操作这些图片 URL 数据,因此这张图片表的操作可以与干货表一同被封装到一个 DAO 中,这样一来 Presenter 对数据库的操作也变得十分简单了。

最终整个 Gank.io App 的架构如下,非常简洁清晰:


总结

整个应用开发下来也遇到了很多小问题,虽然应用需求十分简单,但是能熟练地在有限时间内开发完这样一个小应用也并非一件十分简单的事,它要求我们站在一个更高地层面去设计整个应用的架构、业务逻辑等。在长时间与 API 和细节功能打交道的同时,偶尔也需要做一做这样的小软件,每次都会有不同的收获。

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

推荐阅读更多精彩内容