实施架构一个Android项目
我们先假设一个场景需求:刚有孩子的爸爸妈妈对用照片、视频记录宝宝成长有强烈的意愿,但苦于目前没有一款专门的手机APP做这件事。A公司洞察到市场需求,要求开发团队尽快完成Android客户端的开发。以下模拟团队和工作开展。
- 团队情况:产品经理1人,Android开发2人,服务端开发2人,UI设计1人。
- 开发周期:两个月。
- 工作量:大约50个界面。
- 隐含需求:考虑到用户群体有可能激增的情况,服务端需要有一定的并发能力。
- 前提:原型已设计完成。
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":"" }
![](/assets/blank.gif)
{"code":200,"message":"登录成功","result":{"user":{"userId":1,"nickName":"Leo","email":"Leo@xxx.com","gender":0}} }
![](/assets/blank.gif)
![](/assets/blank.gif)
{"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":"这是小孩第一次出去春游", ......}} }}
![](/assets/blank.gif)
主要API接口设计如下:
![](/assets/blank.gif)
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 ......
![](/assets/blank.gif)
也许你看到了,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
- 在匿名回调中处理请求结果
- 在UI线程外进行http请求
- 文件断点上传
- 智能重试
- 默认gzip压缩
- 支持解析成Json格式
- 可将Cookies持久化到SharedPreferences
2.3.2 云巴推送
- 专注于为需要实时数据交换的产品提供完美解决方案
- 基于发布者/订阅者(publisher/subscriber)模式,集成简单
- 对比了百度云推送、腾讯信鸽推送,云巴效果更好
- 原极光推送CTO创办的
2.3.3 xUtils(只使用其中的DbUtils和ViewUtils)
- Android中的ORM框架,一行代码就可以进行增删改查
- 支持事务,默认关闭
- 可通过注解自定义表名、列名、外键、唯一性约束、NOT NULL约束、CHECK约束等(需要混淆的时候请注解表名和列名)
- Android中的IOC框架,完全注解方式就可以进行UI,资源和事件绑定
- 新的事件绑定方式,使用混淆工具混淆后仍可正常工作
2.3.4 友盟统计
- 国内专业的移动应用统计分析平台
- 统计和分析流量来源、内容使用、用户属性和行为数据
- Crash log跟踪
2.3.5 云通讯验证码
- 解决方案成熟,众多公司使用
2.3.6 高德地图定位
- GPS+基站+wifi的混合定位方式
- 接入简单
2.3.7 异步图片加载库Android-Universal-Image-Loader
- 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
- 支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
- 支持图片的内存缓存,文件系统缓存或者SD卡缓存
- 支持图片下载过程的监听
- 根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
- 较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
- 提供在较慢的网络下对图片进行加载
2.3.8 阿里云OSS Android客户端SDK
- 提供文件(图片、视频等等)上传
- 大文件分块上传
- 删除操作(不推荐在客户端使用)
2.3.9 组件内通讯EventBus
- 基于发布者/订阅者(publisher/subscriber)模式
- 简化了应用程序内各组件间、组件与后台线程间的通信
2.3.10 Android本地数据库加密库SQLCipher
- 基于SQLite扩展的开源数据库,在SQLite的基础之上增加了数据加密功能
- SQLCipher对Android SDK中所有与数据库相关的API都制作了一份镜像,使得开发者可以像操作普遍的数据库文件一样来操作SQLCipher
2.4 基础组件封装
2.4.1 基础回调接口
![](/assets/blank.gif)
public interface DataCallback {void onSuccess(Object result);void onFailure(Object result);}
![](/assets/blank.gif)
2.4.2 网络访问
先看一下登录的序列图:
HttpManager类负责调用AsyncHttpWrapper中的post方法,和对服务端返回的数据解密、JSON转对象、回调上层;AsyncHttpWrapper则负责请求体的封装加密和其它的校验参数封装。看一下HttpManager类的post方法:
![](/assets/blank.gif)
public void post(Context context, String url, RequestParams params, final String modelName,final DataCallback callback) {AsyncHttpWrapper.post(context, url, params, new AsyncHttpResponseHandler() {@Overridepublic 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是200if (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);}}@Overridepublic 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);}}}});}
![](/assets/blank.gif)
AsyncHttpWrapper中的post方法
![](/assets/blank.gif)
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);}
![](/assets/blank.gif)
private static AsyncHttpClient client = new AsyncHttpClient();
2.4.3 Adapter封装
为了加快开发速度,重用代码,Adapter的使用有技巧。每次在getView中查找控件id、利用ViewHolder、赋值,最后返回convertView,看着都是差不多的代码。是时候脱离这个苦海了。先看怎么解决共用的ViewHolder问题。
![](/assets/blank.gif)
public static <T extends View> T get(View view, int id) {SparseArrayCompat<View> viewHolder = (SparseArrayCompat<View>) view.getTag();if (viewHolder == null) {viewHolder = new SparseArrayCompat<>();view.setTag(viewHolder);}View childView = viewHolder.get(id);if (childView == null) {childView = view.findViewById(id);viewHolder.put(id, childView);}return (T) childView;}
![](/assets/blank.gif)
ViewHolder的作用,就是通过convertView.setTag与convertView进行绑定。当convertView复用时,直接从与之对应的ViewHolder(getTag)中拿到convertView布局中的控件,省去了findViewById的时间。上面的代码就是这样的原理。
然后就是CommonAdapter了。
![](/assets/blank.gif)
public abstract class CommonAdapter<T> extends BaseAdapter {protected LayoutInflater inflater;protected Context context;protected List<T> datas;protected final int itemLayoutId;public CommonAdapter(Context context, List<T> datas, int itemLayoutId) {this.context = context;this.inflater = LayoutInflater.from(context);this.datas = datas;this.itemLayoutId = itemLayoutId;}@Overridepublic int getCount() {return datas != null ? datas.size() : 0;}@Overridepublic T getItem(int position) {return datas.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {final CommonViewHolder viewHolder = getViewHolder(position, convertView, parent);convert(viewHolder, getItem(position), position);return viewHolder.getConvertView();} public abstract void convert(CommonViewHolder viewHolder, T item, int position); }
![](/assets/blank.gif)
好了,不贴代码了。看不明白了请点击这里:Android 快速开发系列 打造万能的ListView GridView 适配器
2.4.5 其它Utils封装
如AES128加密类、BitmapUtils、SecurePreferences、StringUtil、ToastUtil、IOUtil等等。
2.5 接口测试
为了保证数据交换、加解密正常,首先对某一个接口进行测试,以验证API Service能正常跑通。比如可以先对登录进行模拟测试,看是否成功,同时包括异常的测试,服务端是不是处理了边界异常,返回给客户端的都是封装过的异常信息,而不是抛一个敏感信息给客户端。提前进行接口测试有助于我们的基础组件运行没问题,方便后期其它模块的快速集成。
2.6 快速开发
基础组件封装好后,除了少量的从网络获取数据逻辑和本地数据库的增删改查,客户端基本上就是界面的布局工作了。界面开发基本看熟练程度和自定义View的重用。
好了,基本就这些。两个Android开发人员两个月内完成肯定是可以的,前提是至少有一个熟手。后面再谈谈MVP,毕竟这个客户端设计没法进行单元测试,如果业务逻辑越来越复杂,Activity的职责会越来越重,问题多多,不利于后期维护。
实施架构一个Android项目相关推荐
- 导入新的一个android项目抱错
导入新的一个android项目抱错,详情见下面: Android requires compiler compliance level 5.0 or 6.0. Found '1.4' instead. ...
- 第一个Android项目
安装玩JDK之后,从Android官方网站直接下载配置好的开发工具即可. 想了解安装过程的话可以搜索教程,一步一步地做.不过这个过程除了漫长,好像也没有别的太大的意义. 好了,开始学习并实践第一个An ...
- Android基础之用Eclipse搭建Android开发环境和创建第一个Android项目(Windows平台)...
原地址:http://www.cnblogs.com/allenzheng/archive/2012/11/10/2762379.html 一.搭建Android开发环境 准备工作:下载Eclipse ...
- 第一个Android项目HelloAndroid
上一篇我已经将开发环境搭建好,现在开始真正的开发之旅. 第一个Android项目HelloAndroid. 1.新建一个Android项目,打开eclipse->file->New-> ...
- android工程的建立,第一个Android项目HelloWorld的建立及剖析
1.建立一个简单的Hello World程序 步骤1:启动Eclipse,选择 New->Other,如下图所示 步骤2:在出现的窗口中选择Android Project,如下图所示: 步骤3: ...
- 导入一个android项目需要改什么意思,导入别人的Android Studio项目前要修改的文件...
AS在导入项目过程中会检查项目中所需的gradle版本 sdk版本等本地是否有, 没有的话就会从官网下载, 众所周知要想从谷歌官网下载东西在我朝是十分困难的.所以需要修改成本地有的.主要改三个个 第一 ...
- 从零开始学android开发-创建第一个android项目
打开ADT开发工具 [file]-[new]-[android application project] 填写application name ,project name,package name,点 ...
- android 项目导入另一个Android项目作为子模块调用
半年前的项目总结刚发现只写了草稿没有发布下
- Android项目架构设计深入浅出
简介:本文结合个人在架构设计上的思考和理解,介绍如何从0到1设计一个大型Android项目架构. 作者 | 璞珂 来源 | 阿里技术公众号 前言:本文结合个人在架构设计上的思考和理解,介绍如何从0到1 ...
最新文章
- UA PHYS515A 电磁理论V 电磁波与辐射6 波导
- 职场思想分享001 | 有多种选择才叫有能力
- 详解 Android 的 Activity 组件
- python能写桌面程序吗_Python新手教程:40行python代码写一个桌面翻译器
- 基于layui的select区域联动
- 信息课为什么不叫计算机课,你理解的互联网是这样的吗?为什么叫加入互联网?...
- 《MySQL必知必会》学习笔记——第三章(了解数据库和表)
- ZK 在XML编辑器中设置.zul文件
- oracle ebs 采购入库,ORACLE EBS/ERP 采购库存模块分录
- 财务系统建设者须知——财务知识碎片(一) 什么是会计科目、什么是会计账户?
- GAN的介绍和简单代码的实现
- 【尚硅谷Java笔记+踩坑】Git(分布式版本控制工具)
- 虚拟服务器主机涨价好多,虚拟主机涨钱了吗
- kpw4换壁纸_kindle 篇五:kindle paperwhite4使用30天总结
- 易开发易投产的51单片机时钟计时器:番茄时钟——TFT彩屏显示方案
- RTABMAP-ROS RGB-D的建图原理
- Java类继承(extends)题目练习,求周长,求面积
- 线性旋转变压器最佳变比求解
- 正高职称 程序员_正高级工程师评审条件最新版
- 基于java springboot vue记账app源码