欢迎分享:http://toutiao.io/contribute 我们先假设一个场景需求:刚有孩子的爸爸妈妈对用照片、视频记录宝宝成长有强烈的意愿,但苦于目前没有一款专门的手机APP做这件事。A公司洞察到市场需求,要求开发团队尽快完成Android客户端的开发。以下模拟团队和工作开展。
1 服务端概要设计1.1 系统架构先给出服务端的架构图。 由于服务端开发有Java、PHP背景,为了快速完成开发任务,我们选择PHP作为服务端开发语言,顺便也把数据库定为MySQL。考虑后期扩展和数据库访问性能,拟引入Redis非关系型数据库。同时为了提高数据读的性能,在云服务器和数据库之间用上缓存,并为数据库主从备份、读写分离。服务器就不搭建在本地了,管理是一大问题。现在云服务器一大把,七牛、阿里云、腾讯云、百度云、金山云等等,技术成熟,而且价格还算公道。在此我们选择阿里云。为了应对可能面临的并发问题,云服务器要考虑负载均衡。项目中可能存在大量的需要上传和下载照片和视频,我们选择阿里云的开放存储服务,同时为了提升各个地区的下载体验,我们引入CDN。客户端通过API Service和服务端交换数据,图片和视频的下载直接通过CDN。 1.2 模块划分根据需求和原型设计,可能的模块划分如下:
1.3 数据交换和API接口服务端与客户端使用JSON交换数据,使用自定义JSON格式,约定返回code、message,实体封装在result中,支持单个实体、实体列表、多个实体列表,定义如下: { "code":500, "message":"系统异常,请稍后重试", "result":"" }
{
"code":200,
"message":"登录成功",
"result":{
"user":{
"userId":1,
"nickName":"Leo",
"email":"Leo@**.com",
"gender":0
}
}
}
{
"code":200,
"message":"SUCCESS",
"result":{
"album":{
"kid":{
"kidId":1,
"nickName":"LEE",
"gender":1,
"birthday":"2014-3-6",
......
},
"media":{
"mediaId":123,
"mediaType":1,
"createdTime":193743546746,
"mediaDescription":"这是小孩第一次出去春游",
......
}
}
}
}
主要API接口设计如下: http://api.**.com/service/v1.0/user/login http://api.**.com/service/v1.0/user/third-login http://api.**.com/service/v1.0/user/register http://api.**.com/service/v1.0/user/logout http://api.**.com/service/v1.0/user/info/update http://api.**.com/service/v1.0/album/upload http://api.**.com/service/v1.0/album/update http://api.**.com/service/v1.0/album/delete ...... 也许你看到了,API做了二级域名映射,同时为了服务端后期API版本的升级管理,在URL中加上了版本标识V1.0。命名方面我尽量做到restful的风格。对了,此处没有使用Https。为了解决数据传输的安全,我做了点特别的处理:对请求体和响应结果进行RSA加密(如果服务端返回的数据稍稍过大,这个RSA严重影响客户端解密,后来我换成了AES),所有请求为POST请求,所以API URL后面没有带参数,你也看不到任何请求相关的信息。 1.4 数据库设计根据需求和原型设计,数据库的设计大概需要两周时间。其实一周基本搞定了,但为了考虑充分,留出一周时间来检验和调整。数据库E-R图略。 2 Android客户端2.1 基本结构Android本身就是MVC,所以我不打算引入MVP或MVVM。我的理念是职责分层,快速推出Android 1.0。主要的包结构如下: 工程的搭建和包的划分有各种各样的,适合自己的就行了。想讨论或想看别人怎么做的,点击这里:App工程结构搭建:几种常见Android代码架构分析 2.2 功能划分注册登录,个人信息,我的小孩,相册管理,消息通知,系统设置等等。 2.3 引入的第三方技术重复发明轮子是不可取的。有些模块根本没必要自己写。以下是引入的第三方库,以及优势说明。 2.3.1 网络请求库android-async-http
2.3.2云巴推送
2.3.3 xUtils(只使用其中的DbUtils和ViewUtils)
2.3.4 友盟统计
2.3.5 云通讯验证码
2.3.6 高德地图定位
2.3.7 异步图片加载库Android-Universal-Image-Loader
2.3.8 阿里云OSS Android客户端SDK
2.3.9 组件内通讯EventBus
2.3.10 Android本地数据库加密库SQLCipher
2.4 基础组件封装2.4.1 基础回调接口public inte**ce DataCallback { void onSuccess(Object result); void onFailure(Object result);
}
2.4.2 网络访问先看一下登录的序列图: HttpManager类负责调用AsyncHttpWrapper中的post方法,和对服务端返回的数据解密、JSON转对象、回调上层;AsyncHttpWrapper则负责请求体的封装加密和其它的校验参数封装。看一下HttpManager类的post方法: public void post(Context context, String url, RequestParams params, final String modelName, final DataCallback callback) {
AsyncHttpWrapper.post(context, url, params, new AsyncHttpResponseHandler() {
@Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { if (modelName != null) {
handleResponse(responseBody, callback, modelName);
} else {
String response = new String(responseBody); // 解密 response = AES128.getInstance().decrypt(AppUtil.decodeReplace(response)); // JSON转对象 BaseMessage message = AppUtil.getMessage(response); if (callback != null) { // 如果自定义code是200 if (Coder.CODE_200.equals(message.getCode())) {
callback.onSuccess(message.getMessage());
} else {
callback.onFailure(new ServerError(message.getCode()));
}
}
}
} catch (JSONException e) {
LogUtil.e(e);
callback.onFailure("服务端返回的数据不能解析成JSON");
} catch (Exception e) {
LogUtil.e(e);
callback.onFailure(e);
}
}
@Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { if (callback != null) {
callback.onFailure(error); if (responseBody != null) {
String s = new String(responseBody);
LogUtil.e(s);
}
}
}
});
}
AsyncHttpWrapper中的post方法 public static void post(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { // 设置请求头部信息 generateHeader(context); // 加密请求参数 String encryParams = AES128.getInstance().encrypt(params.toString());
RequestParams requestParams = new RequestParams();
requestParams.put("param", AppUtil.encodeReplace(encryParams));
client.post(context, url, requestParams, responseHandler);
}
private static AsyncHttpClient client = new AsyncHttpClient(); 2.4.3 Adapter封装为了加快开发速度,重用代码,Adapter的使用有技巧。每次在getView中查找控件id、利用ViewHolder、赋值,最后返回convertView,看着都是差不多的代码。是时候脱离这个苦海了。先看怎么解决共用的ViewHolder问题。 public static |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|