keeganlee 谈 App 架构设计

目录:
  1. App架构设计经验谈:接口的设计
  2. App架构设计经验谈:技术选型
  3. App架构设计经验谈:数据层的设计
  4. App架构设计经验谈:业务层的设计
  5. App架构设计经验谈:展示层的设计

一共分了5篇文章, 下面是对这5篇文章的学习笔记.
对这5篇文章的笔记总结的顺序是从后到前.

http://keeganlee.me/post/architecture/20160222

App架构设计经验谈:展示层的设计

摘录:
保持简洁性:保持代码和结构的简洁,每个方法,每个类,每个包,每个文件,都不要塞太多代码或资源,感觉多了就应该拆分。

像 ChromeTabbedActivity.java, 写了3670行, 就需要进行拆分重构.

工程结构其实就是模块的划分,无非分为两类:按业务划分或按组件划分。

如果采用业务划分模块, 弊端是对于新的开发人员, 因为对业务并不熟悉, 可能会把自己写的类归入到了错误模块, 造成代码混乱.

因此,我更喜欢按组件划分的工程结构,因为组件每个人都懂,不管对业务熟不熟悉,查找起来都明显方便很多。Android按组件划分大致如下:

com.domain.activities 存放所有的Activity
com.domain.fragments 存放所有的Fragment
com.domain.adapters 存放所有的Adapter
com.domain.services 存放所有的Service
com.domain.views 存放所有的自定义View
com.domain.utils 存放所有的工具类

基类的定义
Android的Activity、Fragment、Adapter, 分别定义一个基类,将大部分通用的变量和方法定义和封装好,将减少很多工作量,而且有了统一的设置,也会减少代码的混乱。

例如浏览器代码里的ActivityBase.java封装夜间模式的处理.

public class ActivityBase implements IThemeModeListener, IOrientationListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    ThemeModeManager.getInstance().addThemeModeListener(this, false);
    //注册ThemeModeManager, 监听夜间模式的改变.
    }

    @Override
    public void onThemeModeChanged(boolean isNightMode, int themeType, 
                                    String themeId) {
        //子类通过重写这个方法, 根据自己的具体情况改变view的背景色, 背景图, 
        //以及文字颜色.
    }

每个Activity的onCreate()方法,一般分为三步:

变量的初始化;
View的初始化;
加载数据。
因此,其实可以将onCreate()方法拆分成三个方法:

initVariables()
initViews()
loadData()

这是一个很好的编码模式, 让onCreate()代码更加的清晰简洁.

http://keeganlee.me/post/architecture/20160214
App架构设计经验谈:业务层的设计

业务层的职责

业务层向下,与数据层交互;向上,与展示层交互。

向下获取数据, 自己处理加工数据, 向上, 给展示层提供接口供其调用,让展示层获取处理后的数据.

与数据层交互只是调用数据层的接口获取数据,而与展示层交互则需要提供接口给展示层调用。因为业务处理一般属于比较耗时的操作,主要在于底层的网络请求比较耗时,所以提供给展示层的接口数据结果应该以异步的方式提供,因此,接口上就需要提供个回调参数,返回业务处理之后的结果。

提供给展示层的接口数据结果应该以异步的方式提供

举个例子:
第一个是新用户注册的例子。注册时,界面上一般都会要求用户输入手机号、验证码、密码和确认密码。但是,API接口一般只会有三个参数:手机号、验证码和密码,不会有确认密码。因此,调用接口之前,密码和确认密码的一致性检查是必须的。同时,也要检查这些数据是否为空、手机号是否符合规范、验证码是否有效、密码有没有包含了特殊字符等。正确姿势就是当所有检查都通过了之后,才调用API接口。最后,调用注册接口成功后,可能还要再调用一次登录接口,并可能将用户登录信息缓存起来,方便用户下次启动应用时自动登录。所有这些都属于业务逻辑处理,也就是业务层的工作。

业务层的职责, 具体要干什么工作, 这点要理解清晰.

其实,只要理解清楚了业务层的职责,业务层就不难实现。

http://keeganlee.me/post/architecture/20160120

App架构设计经验谈:数据层的设计

一个App,从根本上来说,就是对数据的处理,包括数据从哪里来、数据如何组织、数据怎么展示,

从职责上划分就是:数据管理、数据加工、数据展示。相对应的也就有了三层架构:数据层、业务层、展示层。本文就先讲讲数据层的设计。

数据层,是三层架构中的最底层,负责数据的管理。它主要的任务就是:

调用网络API,获取数据;
将数据缓存到本地;
将数据交付给上一层。
根据这三个任务,数据层可以再拆分为三层:网络层、本地数据层、交付层。

数据层, 又分为3层: 网络层、本地数据缓存层、交付层

参考Android项目重构之路系列的架构篇和实现篇,其中接口层和本文的网络层是一样的。
还有一些在前面的文章中没有提及到的,在此做一些补充。

首先是不同网络状态的处理。当网络不可用时,则不应该再去调用API;当网络可用,但不是WIFI时,有些比较耗流量的操作也应该禁止,比如上传和下载大文件;当网络状态不同时,还可以采用不同的网络策略,比如,当网络为WIFI时,当前API可以返回更多更全面的数据,还可以预先加载相关联的其他API。

根据手机的网络状态, 对API的调用要做相应的调整.
蜻蜓FM的开屏广告只有在wifi时预先加载, 这个策略就挺不错的.

其次,为了节省流量,接口的设计上可以对数据进行简化。例如,对于一些列表类的接口,可以这么设计:只返回更新的部分,比如,上一次请求返回了10条按时间排序的数据,第一条数据为最新的,id为101,当发起下一次请求时,将101的id作为参数调用API,API查到该id,发现该id之后又新增了两条数据,API则只返回新增的这两条数据。

对于listview中的数据, 给每条数据设置的id就是干这个用的, 这个设计思想非常有用.
为节省流量, 采用这种方式去设计接口, 是很有必要的.

另外,为了保证程序的健壮性,调用API时,对入参的合法性检查也是很有必要的。而且,也应该定义好本地的错误码和错误信息,保证每个错误都能正常解析。

参数的合法性检查, 以及错误码处理.

本地数据层
本地数据的缓存要考虑3个问题:
<1>哪些需要缓存?哪些不需要缓存?

将所有数据都缓存是不明智的,不同的数据应该有不同的缓存策略. 判断标准可以是:用户查看该数据的频率高不高?
比如一个电商App,首页的商品列表数据应该缓存,而且缓存时间应该比较长,而每个商品的详情数据就没必要缓存或缓存时间很短。

<2>缓存在哪里?数据库?文件?还是内存?

内存非常有限。因此,内存一般只用来缓存使用频率非常高的数据.
文件缓存主要就是图片、音频、视频了.
数据库可以保存大量数据,主要就是用于保存商品列表、聊天记录之类的关系型数据.
然而,不管缓存在哪里,都需要限定好缓存的容量,要定期清理,不然会越积越多。

<3>缓存时间多长?
首先,每份缓存数据都应该设置一个缓存的有效时间,有效期的起始时间以最后一次被调用的时间为准,当该数据长时间没有再被调用到时,就应该从缓存中清理掉。
缓存的有效时间应该设多长呢?可以短至一分钟,长至一星期甚至一个月,具体因数据而异。一般内存的缓存时间不宜太长,程序退出基本就要全部清理了。
文件缓存可以设置保留一天或一个星期,可以每隔一天清理一次。数据库缓存再久一些也无所谓,但最好还是不要超过一个月。

交付层
上层向数据层请求数据,它是不关心数据层的数据是从缓存获取还是从网络获取的,它只关心结果,数据层能给到它想要的数据结果就OK了。因此,交付层主要就是定义一堆开放的接口或协议。

上层只关心请求数据的结果, 具体是从缓存来的还是从网络获取的并不关心.

http://keeganlee.me/post/architecture/20160114

App架构设计经验谈:技术选型

MVC/MVP/MVVM
架构模式上,我不会推崇说哪种模式好,每种模式都各有优点,也各有极限性。越高级的模式复杂性越高,实现起来也越难。最近火热的微服务架构,比起MVC,复杂度不知增加了多少倍。

我在实际项目中思考架构时,也不会想着要用哪种模式,我只思考现阶段,以现有的人力资源和时间资源,如何才能更快更好地完成需求,适当考虑下如何为后期扩展或重构做准备。
技术选型,决策关键不在于每种技术方案的优劣如何,而在于你团队的水平、资源的多寡,要根据实际情况选择最适合你们当前阶段的架构方案。
当团队拓展了,资源也充足了,肯定也是需要再重构的,到时再思考其他更合适更优秀的方案。

http://keeganlee.me/post/architecture/20160107

App架构设计经验谈:接口的设计

解释了什么是RESTFul, 什么是token.

现在,大部分App的接口都采用RESTful架构,RESTFul最重要的一个设计原则就是,客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上身份验证信息。实现上,大部分都采用token的认证方式,一般流程是:

  1. 用户用密码登录成功后,服务器返回token给客户端;
  2. 客户端将token保存在本地,发起后续的相关请求时,将token发回给服务器;
  3. 服务器检查token的有效性,有效则返回数据,若无效,分两种情况:
    token错误,这时需要用户重新登录,获取正确的token
    token过期,这时客户端需要再发起一次认证请求,获取新的token

客户端使用token的流程要记清楚.

然而,此种验证方式存在一个安全性问题:当登录接口被劫持时,黑客就获取到了用户密码和token,后续则可以对该用户做任何事情了。用户只有修改密码才能夺回控制权。

单纯使用token存在安全问题, 需要进行优化.

如何优化呢?第一种解决方案是采用HTTPS。HTTPS在HTTP的基础上添加了SSL安全协议,自动对数据进行了压缩加密。
一般,只有安全要求比较高的系统才会采用HTTPS,比如银行。而大部分对安全要求没那么高的App还是采用HTTP的方式。

作者在项目中的实际使用方案是: 登录成功后给客户端分配一个密匙, 客户端发调用API时, 根据密匙和API参数生成一个签名值, 把这个签名值作为API的参数发给服务器.

给客户端分配一个密钥,每次请求接口时,将密钥和所有参数组合成源串,根据签名算法生成签名值,发送请求时将签名一起发送给服务器验证。
类似的实现可参考OAuth1.0的签名算法。这样,黑客不知道密钥,不知道签名算法,就算拦截到登录接口,后续请求也无法成功操作。不过,因为签名算法比较麻烦,而且容易出错,只适合对内的接口。如果你们的接口属于开放的API,则不太适合这种签名认证的方式了,建议还是使用OAuth2.0的认证机制。

另外,现在越来越多App取消了密码登录,而采用手机号+短信验证码的登录方式,我在当前的项目中也采用了这种登录方式。这种登录方式有几种好处:

  1. 不需要注册,不需要修改密码,也不需要因为忘记密码而重置密码的操作了;
  2. 用户不再需要记住密码了,也不怕密码泄露的问题了;
  3. 相对于密码登录其安全性明显提高了。

现在越来越多的app采用手机号+短信验证码的方式实现登录操作.
像膜拜单车这样的app就只保留了手机号+短信验证码的登录方式, 是个不错的实践.

接口数据的设计
接口的数据一般都采用JSON格式进行传输
服务器返回的数据结构,一般为:

{
    code:0,
    message: "success",
    data: { key1: value1, key2: value2, ... }
}
code: 返回码,0表示成功,非0表示各种不同的错误
message: 描述信息,成功时为"success",错误时则是错误信息
data: 成功时返回的数据,类型为对象或数组

不同错误需要定义不同的返回码,属于客户端的错误和服务端的错误也要区分,比如1XX表示客户端的错误,2XX表示服务端的错误。这里举几个例子:

0:成功
100:请求错误
101:缺少appKey
102:缺少签名
103:缺少参数
200:服务器出错
201:服务不可用
202:服务器正在重启

接口版本的设计
为了适应接口发生的变化,必须得做接口版本的设计。实现上,一般有两种做法:

<1> 每个接口有各自的版本,一般为接口添加个version参数。
<2> 整个接口系统有统一的版本,一般在URL中添加版本号,比如http://api.domain.com/v2

大多数对外开放的API采用直接在URL中写明版本号的方式.

这个系列文章写的确实是很好, 值得好好学习.

-----DONE.-------------------------

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

推荐阅读更多精彩内容