一个互联网app的开发设计(技术选型和架构)
原文地址:http://blog.csdn.net/brycegao321/article/details/51830525
在做一个互联网应用时, 要考虑技术选型和架构搭建。 先说说技术选型, 以丁丁租房为例在开发时会面对如下问题:
1、图片处理, image loader、picasso、Glide、Fresco, 推荐使用fresco,因为它使用三级缓存、占内存更小;
2、http通信, 开源框架有很多例如volley,retrofit,okhttp等等, 用法都很简单也类似, 推荐使用OkHttp,它支持SPDY;
3、崩溃日志采集, 免费库也有很多, 腾讯bugly、友盟、Fabric。我们使用的是Fabric, 挺好用的。
4、即时聊天, 三方库也有很多, 就不多说了。 我们用的是leancloud, 因为它免费:), 而且效果还可以。
5、控件, 根据UI需要可以自己写或用别人写好的三方库, 例如Materialdialog,MPAndroidChart, WheelView等等。
6、进程内部消息传递, 例如跟Service交互的binder、或者LocalbroadcastReceiver(基于主线程handler实现的,所以不会被其他进程收到,比较安全!)、三方库EventBus和观察者模式等等。
7、支付, 可以用支付宝、微信的接口。
8、埋点, 这个就很重要了, 产品经理每天都在盯着这个统计数据, 用他们的话叫做数据引导决策, 有时前端也会弄ABTest, 目的是做出让用户更喜欢的东西。 我们用了Countly, 因为它开源, 后台可以自己做, 埋点数据可以传到自己的服务器。 毕竟一些敏感的数据是不想让三方如友盟知道的。
9、bug热修复, 目前有很多技术方案, 我们参考了HotFix的方式在项目中落地, 网上有人说适配有问题, 但我们还没碰到。。。
10、插件化, 应用典型案例就是支付宝、微信。当app功能非常多、代码量很大(超过65535个方法)时要考虑, 我们是计划实践一下, 但因为公司突然倒闭, 没来得及做。
11、程序框架, 对比mvc、mvp和mvvm模式, 我觉得mvp模式比较合适并落地到丁丁租房app里, 因为mvp很好的实现了代码解耦、逻辑分层, 下层对上层透明, 每层只关心自己那点事情就够了。
12、UI标注,推荐使用标你妹啊网站、app.zeplin.io网站。
下面丁丁租房架构图, 最上面一层是各个功能。
代码结构如下, 按照业务划分一级目录, 二级目录是按照android各组件区分。
从二级目录看到activity实例化presenter并保存该引用, presenter通过Activity传进来的interface回调到Activity, presenter实现网络接口。
调试网络接口要说一下, 在Activity/Fragment退出时要取消未完成的网络请求, 避免耗费流量和回调刷ui时的异常; 可以用RestClient在浏览器测试接口,目的是理解和调试接口的有效性;
下面附干货源码, 供参考:
Model代码:
/** * http网络相关工具类, 目前使用三方库OkHttp库 */ public class NetworkUtils {//网络接口执行周期打点 private static class DotNet {public String tag; //网络接口的标签 public long timestamp; //时间戳 public String uuid; }private static OkHttpClient sInstance; //初始化OkHttp实例 static {sInstance = new OkHttpClient(); sInstance.setConnectTimeout(10, TimeUnit.SECONDS); //连接超时时间 sInstance.setWriteTimeout(10, TimeUnit.SECONDS); //上传文件超时时间 sInstance.setReadTimeout(20, TimeUnit.SECONDS); //下载文件超时时间 }/** * 格式化请求body * * @param object, 查询用的参数,Map or entity */ private static RequestBody formatRequestBody(Context context, Object object) {//JSON.toJSONString(object)方法默认执行去除value为null的字段 /*Iterator iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); //编辑房源时可以传空字符串 if (entry.getValue() == null) { iterator.remove(); } }*/ LinkedHashMap baseMap = GatewayUtils.getInstance().getBase(context); //获取base参数 Iterator it = baseMap.entrySet().iterator(); while (it.hasNext()) {Map.Entry entry = (Map.Entry) it.next(); if (entry.getValue() == null || StringUtils.isNull(entry.getValue().toString())) {it.remove(); }}String param = JSON.toJSONString(object); //生成param参数 String sign = ParamBuild.getSign(object, baseMap); //计算sign参数 baseMap.put("sign", sign); String base = JSON.toJSONString(baseMap); RequestBody formBody = new FormEncodingBuilder().add("base", base).add("param", param).build(); return formBody; }/** * 格式化请求body * * @param object, 查询用的参数,Map or entity * @param dotNet, 打点要用的参数 */ private static RequestBody formatRequestBodyExt(Context context, Object object, DotNet dotNet) {//JSON.toJSONString(object)方法默认执行去除value为null的字段 /*Iterator iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); //编辑房源时可以传空字符串 if (entry.getValue() == null) { iterator.remove(); } }*/ LinkedHashMap baseMap = GatewayUtils.getInstance().getBase(context); //获取base参数 Iterator it = baseMap.entrySet().iterator(); while (it.hasNext()) {Map.Entry entry = (Map.Entry) it.next(); if (entry.getValue() == null || StringUtils.isNull(entry.getValue().toString())) {it.remove(); }}try {//网络接口执行周期打点时的参数 String uuid = (String) baseMap.get("uuid"); dotNet.timestamp = System.currentTimeMillis(); dotNet.uuid = uuid; } catch (Exception ex) {ex.printStackTrace(); }String param = JSON.toJSONString(object); //生成param参数 String sign = ParamBuild.getSign(object, baseMap); //计算sign参数 baseMap.put("sign", sign); String base = JSON.toJSONString(baseMap); RequestBody formBody = new FormEncodingBuilder().add("base", base).add("param", param).build(); return formBody; }/** * 与服务器异步交互, 并返回结果字符串 * * @param ctx 应用上下文 * @param url 服务器url地址 * @param map 查询参数 * @param tag 标签,用于取消请求 * @param listener 回调 */ public static void asyncWithServer(final Context ctx, String url, Map map, String tag, final OnNetworkListener listener) {if (ctx == null || url == null || listener == null || tag == null || map == null ) {return; }DotNet dotNet = new DotNet(); dotNet.tag = tag; final RequestBody requestBody = formatRequestBodyExt(ctx, map, dotNet); //组织请求包体 Request request = new Request.Builder().tag(dotNet).url(url).addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8").post(requestBody).build(); sInstance.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Request request, IOException e) {DotNet dotTag = (DotNet)request.tag(); listener.onFailure(dotTag.tag); }@Overridepublic void onResponse(Response response) throws IOException {DotNet dotTag = (DotNet)response.request().tag(); if (!response.isSuccessful()) {//失败 listener.onFailure(dotTag.tag); } else {//Countly打点, 统计接口执行周期 try {dotInterfaceTag(ctx, dotTag.timestamp, dotTag.uuid, response.request().urlString()); } catch (Exception ex) {ex.printStackTrace(); }String jsonBody = response.body().string(); //添加json解析异常逻辑,https://fabric.io/cb5ab5/android/apps/com.dingding.client/issues/561cf641f5d3a7f76bc3e0df Map<String, Object> map = null; try {map = JSON.parseObject(jsonBody); } catch (JSONException ex) {ex.printStackTrace(); } finally {if (map == null || map.size() == 0) {listener.onFailure(dotTag.tag); //解析失败时按照接口失败处理 return; }}//更新token, 可能引起性能问题, 相当于做了2次json解析 if (map.containsKey("token")) {String token = (String) map.get("token"); GatewayUtils.getInstance().setToken(ctx, token); }Integer code = (Integer) map.get("code"); if (code == 100016) {//code为100016时为token过期 重新登录 GatewayUtils.getInstance().setDefult(ctx); Intent intent = new Intent(); intent.setAction("com.dingding.client.NewLoginActivity"); intent.putExtra("code", code); intent.putExtra("from", "2"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(intent); }listener.onSuccess(jsonBody, dotTag.tag); }}}); }/** * 与服务器异步交互, 并返回类对象 * * @param ctx 应用上下文 * @param url 服务器url地址 * @param map 查询参数 * @param tag 标签,用于取消请求 * @param listener 回调 * @param clz, 解析结果类 可以传入null null是为了考虑有些返回值没有data节点 * @param isList ,传入true则返回解析为列表,传入false则返回为对象 */ public static void asyncWithServerExt(Context ctx, String url, Map map, String tag, final OnNetworkListener listener, final Class<?> clz, final boolean isList) {asyncWithServerExt(ctx, url, map, tag, listener, clz, isList, null); }/** * 与服务器异步交互, 并返回类对象 * * @param ctx 应用上下文 * @param url 服务器url地址 * @param object 查询参数,Map or entity * @param tag 标签,用于取消请求 * @param listener 回调 * @param clz, 解析结果类 可以传入null null是为了考虑有些返回值没有data节点 * @param isList ,传入true则返回解析为列表,传入false则返回为对象 */ public static void asyncWithServerExt(final Context ctx, String url, Object object, String tag, final OnNetworkListener listener, final Class<?> clz, final boolean isList, final String key) {if (ctx == null || url == null || listener == null || tag == null || object == null ) {return; }DotNet dotNet = new DotNet(); dotNet.tag = tag; RequestBody requestBody = formatRequestBodyExt(ctx, object, dotNet); //组织请求包体, 会修改时间戳和uuid Request request = new Request.Builder().tag(dotNet).url(url).addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8").post(requestBody).build(); sInstance.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Request request, IOException e) {DotNet dotNet = (DotNet) request.tag(); listener.onFailure(dotNet.tag); }@Overridepublic void onResponse(Response response) throws IOException {DotNet dotTag = (DotNet) response.request().tag(); if (!response.isSuccessful()) {//失败 listener.onFailure(dotTag.tag); } else {//Countly打点, 统计接口执行周期 try {dotInterfaceTag(ctx, dotTag.timestamp, dotTag.uuid, response.request().urlString()); } catch (Exception ex) {ex.printStackTrace(); }//json解析并回调 DataFactory factory = new ResultObjectFactory(); ResultObject result; if (clz == null) {result = factory.createResultObjectNoCls(ctx, response.body().string()); } else {result = factory.createResultObject(ctx, isList, response.body().string(), clz, key); }if (result == null) {listener.onFailure(dotTag.tag); } else {listener.onSuccessExt(result, dotTag.tag); }}}}); }/** * Countly打点计算每个接口的执行周期 * @param url, 请求的url */ private static void dotInterfaceTag(Context ctx, long timestamp, String uuid, String url) {long diff = Math.abs(System.currentTimeMillis() - timestamp); HashMap<String, Object> paramMap = new HashMap<>(); paramMap.put("uuid", uuid); paramMap.put("time", diff + ""); paramMap.put("url", url); Log.d("NetPerformance", "url:" + url + ", diff:" + diff); Statistics.dotNetPerformance(ctx, paramMap); }/** * 与服务器异步交互, 并返回类对象 * * @param ctx 应用上下文 * @param url 服务器url地址 * @param map 查询参数 * @param tag 标签,用于取消请求 * @param listener 回调 * @param clz, 解析结果类 可以传null */ public static void asyncWithServerExt(Context ctx, String url, Map map, String tag, final OnNetworkListener listener, final Class<?> clz) {asyncWithServerExt(ctx, url, map, tag, listener, clz, false); }/** * 与服务器同步交互, 必须在非UI线程执行! */ public static String syncWithServer(Context ctx, String url, Map map) {if (ctx == null || url == null || map == null) {return ""; }String retStr = null; //服务器返回的包体 RequestBody requestBody = formatRequestBody(ctx, map); //组织请求包体 Request request = new Request.Builder().url(url).addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8").post(requestBody).build(); Response response; try {response = sInstance.newCall(request).execute(); if (response != null && response.isSuccessful()) {retStr = response.body().string(); //包体 }} catch (IOException ex) {ex.printStackTrace(); } finally {}return retStr; }//返回ResultObject public static ResultObject syncWithServer(Context ctx, String url, Map map, final Class<?> clz) {RequestBody requestBody = formatRequestBody(ctx, map); //组织请求包体 Request request = new Request.Builder().url(url).addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8").post(requestBody).build(); ResultObject result; Response response; DataFactory factory; try {response = sInstance.newCall(request).execute(); factory = new ResultObjectFactory(); result = factory.createResultObject(ctx, false, response.body().string(), clz, null); } catch (IOException ex) {result = new ResultObject(); result.setCode(-1); result.setSuccess(false); result.setMessage("网络错误"); }return result; }
Presenter代码:
/** * Presenter基类 */ public abstract class BasePresenter {public HashMap<String, Object> mKeyMap= new HashMap<String, Object>(); //请求的参数 public HashMap<String, Object> mFilterMap= new HashMap<String, Object>(); //请求的参数 public Handler mHandler = new Handler(); /** * UI主线程handler, 用于更新view */ protected HashSet<String> mTagList = new HashSet<String>(); public IBaseView mIView; /** * 与后台交互时的参数 * * @return */ public abstract HashMap<String, Object> getParams(); /** * 获取应用上下文 */ public abstract Context getContext(); /** * 设置应用上下文, 使用ApplicationContext */ public abstract void setContext(Context ctx); /** * 设置tag, 用于删除队列中的网络请求, 也可以区分不同的接口 * * @param tag */ public abstract void setTag(String tag); /** * 获取tag, 用于删除队列中的网络请求,也可以区分不同的接口 */ public abstract String getTag(); /** * 获取回调函数的引用, 不同业务的逻辑不同 */ public abstract OnNetworkListener getListener(); /** * 设置View引用,派生类可以扩展IBaseView接口类, 在activity里实例化,并通过强制类转换赋值。 */ public void setView(IBaseView view) {mIView = view; }/** * 获取View引用, 派生类可以扩展IBaseView接口类,在activity里实例化,并通过强制类转换赋值。 */ public IBaseView getView() {return mIView; }/** * 在ui主线程执行更新界面的操作 */ public void updateUI(Runnable runnable) {mHandler.post(runnable); }/** * 清空参数 */ public void resetParams() {mKeyMap.clear(); mFilterMap.clear(); }/** * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。 * 回调函数会执行onSuccess(String body) * * @param url, 网址 * @param listener, model层的回调。 为空时使用getListener() * @return boolean, 是否成功传递到model层 */ protected boolean asyncWithServer(String url, OnNetworkListener listener) {String tag = getTag(); Context ctx = getContext(); OnNetworkListener callback; if (listener == null) {callback = getListener(); } else {callback = listener; }if (tag == null || callback == null || ctx == null) {return false; }mTagList.add(tag); NetworkUtils.asyncWithServer(ctx, url, getParams(), tag, callback); return true; }/** * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。 * 回调函数会执行onSuccess(ResultObject result) * * @param url, 网址 * @param clz, 解析的类名 * @param listener, model层的回调。 为空时使用getListener() * @return boolean, 是否成功传递到model层 */ protected boolean asyncWithServerExt(String url, Class<?> clz, OnNetworkListener listener) {return asyncWithServerExt(url, clz, listener, false); }/** * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。 * 回调函数会执行onSuccess(ResultObject result) * * @param url, 网址 * @param clz, 解析的类名 * @param listener, model层的回调。 为空时使用getListener() * @param isList ,传入true则返回解析为列表,传入false则返回为对象 * @return boolean, 是否成功传递到model层 */ protected boolean asyncWithServerExt(String url, Class<?> clz, OnNetworkListener listener, final boolean isList) {return asyncWithServerExt(url, clz, listener, isList, null); }/** * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。 * 回调函数会执行onSuccess(ResultObject result) * * @param url, 网址 * @param clz, 解析的类名 * @param listener, model层的回调。 为空时使用getListener() * @param isList ,传入true则返回解析为列表,传入false则返回为对象 * @return boolean, 是否成功传递到model层 */ protected boolean asyncWithServerExt(String url, Class<?> clz, OnNetworkListener listener, final boolean isList, final String key) {String tag = getTag(); Context ctx = getContext(); OnNetworkListener callback; if (listener == null) {callback = getListener(); } else {callback = listener; }if (tag == null || callback == null || ctx == null) {return false; }mTagList.add(tag); NetworkUtils.asyncWithServerExt(ctx, url, getParams(), tag, callback, clz, isList, key); return true; }/** * 释放当前presenter的请求 * 在activity或fragment的onDestory函数中调用 */ public void cancelRequests() {for (Iterator<String> iterator = mTagList.iterator(); iterator.hasNext(); ) {String tag = iterator.next(); NetworkUtils.cancelRequestByTag(tag); }}
一个互联网app的开发设计(技术选型和架构)相关推荐
- 小米资深工程师瞿晋萍(男):米聊服务器的技术选型和架构设计
小米资深工程师瞿晋萍:米聊服务器的技术选型和架构设计 - 资讯频道 - CSDN.NET 小米资深工程师瞿晋萍:米聊服务器的技术选型和架构设计 2012-07-07 11:04 | 238次阅读 | ...
- 电子商务平台技术选型和架构设计
作者:禅与计算机程序设计艺术 1.简介 在这个时代,在线购物网站和电子商务平台已经成为促进互联网经济增长.服务用户和客户的重要平台.如何搭建一个具备高可用性.安全可靠的电商系统,是一个复杂的工程.作为 ...
- 论在开发中技术选型的重要性
今天国庆假期后的第一个工作日晚上跟朋友聊天他说他部门的一个同事今天提离职申请. 先来说说这位同事,他毕业两年一份工作经历,面试时候很自信,对每个知识点都有自己的理解,没有死记硬背.这也是我那位项目经理 ...
- 米聊服务器的技术选型和架构设计
http://wenku.it168.com/d_000434507.shtml米聊服务器的技术选型和架构设计
- [短彩信]C#短彩信模块开发设计(1)——架构
准备从以下几个方面简单的谈谈短彩信模块的实现: [短彩信]C#短彩信模块开发设计(1)--架构(http://www.cnblogs.com/CopyPaster/archive/2012/12/07 ...
- 小米科技-瞿晋萍-米聊技术选型与架构
小米科技-瞿晋萍-米聊技术选型与架构 可拜上见,ppt需仔细研究 posted on 2012-07-11 11:09 lexus 阅读( ...) 评论( ...) 编辑 收藏 转载于:https ...
- 千人千面、用户画像的设计、技术选型与架构实现
用户画像的目的是为产品筛选出目标客户 目前,越来越多的企业,在大数据应用上,都会选择用户画像这一主题,为什么呢?因为用户画像相对于做推荐以及机器学习等简单容易的多,做画像,更多是就是对用户数据的整合, ...
- app跨平台开发框架以及技术选型如何选择?
作者:刘望舒 链接:https://www.zhihu.com/question/55714900/answer/665044650 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转 ...
- application APP定制开发设计
第一:APP开发是啥 App开发设计,就是指致力于手机[说文解字[卷六][木部]机机:机通"积","几"即数量,意取累积,积木生机,助缘合因,至取正果.]上系统 ...
最新文章
- 784.字母大小写全排列
- android 拍照空指针,空指针异常时嵌入照片中的Android
- Python 爬取简单网页
- (十)Java B2B2C o2o多用户商城 springcloud架构- SSO单点登录之OAuth2.0登录认证(1)
- try-with-resource语法
- 协同过滤算法_推荐系统(5):协同过滤算法的基本思想
- 宝塔 php5.6 极速安装_cltphp5.6.6版本安装教程-宝塔练手
- 利用sendmail搭建电子邮件服务器
- grpc框架_grpc的入门使用
- 《流畅的Python》读书笔记——Python一等函数
- 写给《我也能做CTO》作者的一封信
- mysql explain命令解析_详解MySQL中EXPLAIN解释命令
- 基于最小错误率的贝叶斯决策(matlab实验)
- python中while循环只能用来实现无限循环的编程_while循环只能实现无限循环的编程...
- 元器件——稳压管Zener的应用总结(稳压原理与应用、主要参数、限流电阻如何选取)
- windows下Graphviz安装及入门教程(附下载链接)
- Python的堆与优先队列
- 快速入门:Excel中如何按照多个字段排序
- 新手也能看懂,Kubernetes其实很简单
- Python处理视频文件的实用姿势
热门文章
- 谢文东的一些话,很有道理
- 树莓派使用红外收发(一):设备安装
- CVPR 2022 | 重新审视池化:你的感受野不是最理想的
- ESP8266(ESP-12F)+DS18B20+贝壳物联
- 基础工资提高至35万美元、带薪病假天数翻倍,亚马逊、苹果为留人才又出新动作
- 中国少儿模特明星盛典 《荣耀王者》主题曲 即将全网发布
- 如何利用校园邮箱免费使用IDEA
- Ajax上传文件的cache、processdata、contentType属性以及FormData对象的总结
- 安徽信息技术初中会考上机考试模拟_初中会考信息技术考试模拟试题一
- 腾讯云在线WebShell终端使用体验