keeganlee 谈 App 架构设计
目录:
- App架构设计经验谈:接口的设计
- App架构设计经验谈:技术选型
- App架构设计经验谈:数据层的设计
- App架构设计经验谈:业务层的设计
- 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的认证方式,一般流程是:
- 用户用密码登录成功后,服务器返回token给客户端;
- 客户端将token保存在本地,发起后续的相关请求时,将token发回给服务器;
- 服务器检查token的有效性,有效则返回数据,若无效,分两种情况:
token错误,这时需要用户重新登录,获取正确的token
token过期,这时客户端需要再发起一次认证请求,获取新的token
客户端使用token的流程要记清楚.
然而,此种验证方式存在一个安全性问题:当登录接口被劫持时,黑客就获取到了用户密码和token,后续则可以对该用户做任何事情了。用户只有修改密码才能夺回控制权。
单纯使用token存在安全问题, 需要进行优化.
如何优化呢?第一种解决方案是采用HTTPS。HTTPS在HTTP的基础上添加了SSL安全协议,自动对数据进行了压缩加密。
一般,只有安全要求比较高的系统才会采用HTTPS,比如银行。而大部分对安全要求没那么高的App还是采用HTTP的方式。
作者在项目中的实际使用方案是: 登录成功后给客户端分配一个密匙, 客户端发调用API时, 根据密匙和API参数生成一个签名值, 把这个签名值作为API的参数发给服务器.
给客户端分配一个密钥,每次请求接口时,将密钥和所有参数组合成源串,根据签名算法生成签名值,发送请求时将签名一起发送给服务器验证。
类似的实现可参考OAuth1.0的签名算法。这样,黑客不知道密钥,不知道签名算法,就算拦截到登录接口,后续请求也无法成功操作。不过,因为签名算法比较麻烦,而且容易出错,只适合对内的接口。如果你们的接口属于开放的API,则不太适合这种签名认证的方式了,建议还是使用OAuth2.0的认证机制。
另外,现在越来越多App取消了密码登录,而采用手机号+短信验证码的登录方式,我在当前的项目中也采用了这种登录方式。这种登录方式有几种好处:
- 不需要注册,不需要修改密码,也不需要因为忘记密码而重置密码的操作了;
- 用户不再需要记住密码了,也不怕密码泄露的问题了;
- 相对于密码登录其安全性明显提高了。
现在越来越多的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.-------------------------