从零开始搭建架构实施Android项目

更新时间:2016-01-21 14:28:21 点击次数:2083次

我们先假设一个场景需求:刚有孩子的爸爸妈妈对用照片、视频记录宝宝成长有强烈的意愿,但苦于目前没有一款专门的手机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@xxx.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.xxx.com/service/v1.0/user/login http://api.xxx.com/service/v1.0/user/third-login http://api.xxx.com/service/v1.0/user/register http://api.xxx.com/service/v1.0/user/logout http://api.xxx.com/service/v1.0/user/info/update http://api.xxx.com/service/v1.0/album/upload http://api.xxx.com/service/v1.0/album/update http://api.xxx.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,所以我不打算引入MVPMVVM。我的理念是职责分层,快速推出Android 1.0。主要的包结构如下:

工程的搭建和包的划分有各种各样的,适合自己的就行了。

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 基础回调接口

复制代码
publicinterfaceDataCallback {voidonSuccess(Object result);voidonFailure(Object result); }
复制代码

2.4.2 网络访问

先看一下登录的序列图:

HttpManager类负责调用AsyncHttpWrapper中的post方法,和对服务端返回的数据解密、JSON转对象、回调上层;AsyncHttpWrapper则负责请求体的封装加密和其它的校验参数封装。看一下HttpManager类的post方法:

复制代码
publicvoidpost(Context context, String url, RequestParams params,finalString modelName,finalDataCallback callback) { AsyncHttpWrapper.post(context, url, params,newAsyncHttpResponseHandler() { @OverridepublicvoidonSuccess(intstatusCode, Header[] headers,byte[] responseBody) {try{if(modelName !=null) { handleResponse(responseBody, callback, modelName); }else{ String response=newString(responseBody);//解密response =AES128.getInstance().decrypt(AppUtil.decodeReplace(response));//JSON转对象BaseMessage message =AppUtil.getMessage(response);if(callback !=null) {//如果自定义code是200if(Coder.CODE_200.equals(message.getCode())) { callback.onSuccess(message.getMessage()); }else{ callback.onFailure(newServerError(message.getCode())); } } } }catch(JSONException e) { LogUtil.e(e); callback.onFailure("服务端返回的数据不能解析成JSON"); }catch(Exception e) { LogUtil.e(e); callback.onFailure(e); } } @OverridepublicvoidonFailure(intstatusCode, Header[] headers,byte[] responseBody, Throwable error) {if(callback !=null) { callback.onFailure(error);if(responseBody !=null) { String s=newString(responseBody); LogUtil.e(s); } } } }); }
复制代码

AsyncHttpWrapper中的post方法

复制代码
publicstaticvoidpost(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {//设置请求头部信息generateHeader(context);//加密请求参数String encryParams =AES128.getInstance().encrypt(params.toString()); RequestParams requestParams=newRequestParams(); requestParams.put("param", AppUtil.encodeReplace(encryParams)); client.post(context, url, requestParams, responseHandler); }
复制代码
privatestaticAsyncHttpClient client =newAsyncHttpClient();

2.4.3 Adapter封装

为了加快开发速度,重用代码,Adapter的使用有技巧。每次在getView中查找控件id、利用ViewHolder、赋值,后返回convertView,看着都是差不多的代码。是时候脱离这个苦海了。先看怎么解决共用的ViewHolder问题。

复制代码
publicstatic<TextendsView> T get(View view,intid) { SparseArrayCompat<View> viewHolder = (SparseArrayCompat<View>) view.getTag();if(viewHolder ==null) { viewHolder=newSparseArrayCompat<>(); view.setTag(viewHolder); } View childView=viewHolder.get(id);if(childView ==null) { childView=view.findViewById(id); viewHolder.put(id, childView); }return(T) childView; }
复制代码

ViewHolder的作用,就是通过convertView.setTag与convertView进行绑定。当convertView复用时,直接从与之对应的ViewHolder(getTag)中拿到convertView布局中的控件,省去了findViewById的时间。上面的代码就是这样的原理。

然后就是CommonAdapter了。

复制代码
publicabstractclassCommonAdapter<T>extendsBaseAdapter {protectedLayoutInflater inflater;protectedContext context;protectedList<T>datas;protectedfinalintitemLayoutId;publicCommonAdapter(Context context, List<T> datas,intitemLayoutId) {this.context =context;this.inflater =LayoutInflater.from(context);this.datas =datas;this.itemLayoutId =itemLayoutId; } @OverridepublicintgetCount() {returndatas !=null? datas.size() : 0; } @OverridepublicT getItem(intposition) {returndatas.get(position); } @OverridepubliclonggetItemId(intposition) {returnposition; } @OverridepublicView getView(intposition, View convertView, ViewGroup parent) {finalCommonViewHolder viewHolder =getViewHolder(position, convertView, parent); convert(viewHolder, getItem(position), position);returnviewHolder.getConvertView(); }publicabstractvoidconvert(CommonViewHolder viewHolder, T item,intposition); }
复制代码

好了,不贴代码了。看不明白了请点击这里:Android 快速开发系列 打造万能的ListView GridView 适配器

2.4.5 其它Utils封装

如AES128加密类、BitmapUtils、SecurePreferences、StringUtil、ToastUtil、IOUtil等等。

2.5 接口测试

为了保证数据交换、加解密正常,首先对某一个接口进行测试,以验证API Service能正常跑通。比如可以先对登录进行模拟测试,看是否成功,同时包括异常的测试,服务端是不是处理了边界异常,返回给客户端的都是封装过的异常信息,而不是抛一个敏感信息给客户端。提前进行接口测试有助于我们的基础组件运行没问题,方便后期其它模块的快速集成。

2.6 快速开发

基础组件封装好后,除了少量的从网络获取数据逻辑和本地数据库的增删改查,客户端基本上就是界面的布局工作了。界面开发基本看熟练程度和自定义View的重用。

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

回到顶部
嘿,我来帮您!