网站首页 > 技术文章 正文
Android或者ios原生开发,并发网络请求是个难排解的痛。现在来看,这个问题已经不是问题了,写个文章记录一下,毕竟当年曾经为了这个问题操碎了心。说这个事首先要明白下面这个问题:
痛在哪?——主线程之殇
Android的消息机制恨死人,众所周知,android UI操作只能在主线程进行,而且主线程里不推荐执行作UI操作以外的一切操作,当年Android早期在主线程里请求网络是不会报错的,顶多把界面卡黑屏。现在这么搞会乍样大家都知道的,编译时会有警告,运行时会crash。如何优雅地请求网络数据,然后切换到主线程去更新UI,无数志士仁人为了解决这个问题操碎了心,早期官方推荐用AsnycTask,后来出现了经典的httpAsync,后来又有volley,现在又有Retrofit。现在的小程序员们不用在乎这些问题,只要拿来用用就行,大公司也都有自己成熟的网络库。而且现在hybird、react native很流行,不会涉及到主线程的问题。但是如果把14年以前的项目源码翻出来,运气好还能看到handler满天飞的情况,真是恶心死人。
问题——为什么要并发请求
页面内容太多,难免会需要执行多个请求来展示数据的情况,比如首页,模块多,变化快;再比如审批详情这种页面,需要展示审批本身数据字段,还要展示关联的一些操作流程等等。至于为什么要并发请求,其实不一定并发,只是必须要有一个种手段来管理页面上的网络请求(或是其他一些费时的非主线程操作),为什么要这样呢?因为页面上要弹个进度条对话框呀,网络请求之前显示“加载中,请稍候”,请求结束,把对话框关了,如果只是一个请求,回调里操作一下即可;如果两个请求呢?有些同学咬咬牙,回调里再请求一下网络,然后再回调嵌套一层忍忍就过去了;如果要两个以上呢?谁设计的接口?前端不去找后端拼命才怪。就这么个看似简单的问题,曾经困扰了多少人呀,我估计到现在还困扰着很多人。
如何解决
这个问题,我当年曾经写过一个责任链的框架,专门用来解决多个网络请求的问题,其实就是顺次执行所有的网络请求。之所以不并发执行,是因为考虑到有些接口请求数据可能要依赖其他接口,控制起来太复杂,于是一股脑全部顺次执行了。
后来一个NB的神器出现了,就是RxJava,唉呀我写文章有点啰嗦,说了半天才说到重点了,,Rx是啥不解释了。RxJava可以很好的与Retrofit结合,毕竟是同一家公司的作品嘛。
解决并发请求的思路就是把网络请求的Observable用Observable.merge合并在一起,正常的Retrofit请求是这样的:
ConsultApi consultApi = RetrofitUtils.createApi(ConsultApi.class); Observable observable = consultApi.getConsultDetail(header(), consultId); observable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Object o) throws Exception { // 业务处理 } });
我需要在请求网络前做一些其他操作,另外再弹一个ProgressDialog,于是就是下面这样的:
Observable.just(RemoteSettingUtils.getRemoteSettingUrl()) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(new Function<String, String>() { @Override public String apply(@NonNull String s) throws Exception { showProgressDialog(); return s; } }) .observeOn(Schedulers.io()) .map(new Function<String, String>() { @Override public String apply(@NonNull String s) throws Exception { String baseUrl = loadBaseUrlFromCache(); if (StringUtils.isBlank(baseUrl)) { baseUrl = loadBaseUrl(); } return baseUrl; } }) .observeOn(Schedulers.io()) .flatMap(apiObservableFunction) .observeOn(AndroidSchedulers.mainThread()) .subscribe(consumer, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { LogUtils.e("数据请求出错", throwable); UiUtils.showToast(context, R.string.network_error); allComplete.callback(null); progressDialog.cancel(); } }, new Action() { @Override public void run() throws Exception { allComplete.callback(null); progressDialog.cancel(); } });
这里用到了flatMap,因为项目需要,在请求网络前会检查网络配配置,获取取正式环境的URL,然后重新初始化Retrofit,这里使用flatMap就非常合适了,可以在运行过程中构建observable,例如:
new Function<String, ObservableSource<HttpResponse>> { @Override public ObservableSource<HttpResponse> apply(String baseUrl) throws Exception { ConsultApi consultApi = RetrofitUtils.createApi(ConsultApi.class); ParamMap param = newParam().addParam("size", 10); Reply reply = SysUtils.getLast(dataList); return consultApi.getReplyList(header(), param); } }
使用flatMap的情况下,实现并发请求网络就非常简单了,当然不能简单的Observable.merge,因为不同的请求数据解析是不一样的。此时的flatMap是可以这么写:
new Function<String, ObservableSource<HttpResponse>> { @Override public ObservableSource<HttpResponse> apply(String baseUrl) throws Exception { Observable observable1 = consultApi.getConsultDetail() .observeOn(AndroidSchedulers.mainThread()) .map(new ResponseFunction() { @Override protected void consume(HttpResponse response) { // UI working } }); Observable observable2 = consultApi.getReplyList() .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .map(new ResponseFunction<List>() { @Override protected void consume(HttpResponse<List> httpResponse) { // UI working } }); return Observable.merge(observable1, observable2); }
这样就可以完美实现并发网络请求了,上面的代码中第二个Observable多了一行subscribeOn(Schedulers.newThread())方法,如果不指定,此时默认还是在上文中的Schedulers.io()里执行,而Schedulers.io()是顺序执行的,如果要真正实现并发,必须要用Schedulers.newThread()。
这里可以简单封装一下,flatMap就变成这样了:
new RequestFunction<Object>() { @Override protected ObservableSource<HttpResponse<Object>> createRequestObservable() { Observable observable1 = addResponseFunction(consultApi.getConsultDetail(), new ResponseFunction<ConsultDetail>() { @Override protected void consume(HttpResponse response) { // UI working } }); Observable observable2 = addResponseFunction(consultApi.getReplyList(), new ResponseFunction<List>() { @Override protected void consume(HttpResponse<List> httpResponse) { // UI working } }, true); return Observable.merge(observable1, observable2); } }
这样就清晰得多了,有多少并发处理都不怕了。
总结
并发网络请求其实并不是个强需求,可以通过合理的接口设计或交互设计来避免,实在绕不开嘛可以使用本文的方法。Square真是家很了不起的公司,我看到RxJava后再想想当年写的责任链框架,真是有点剑走偏峰了,我都不好意思说当年还写过那么个东西了。做技术必须要保持开放,切不可坐在井里看天,瞎造轮子。
转载请注明出处
原文链接: http://www.xuding.info/2018/02/14/post602/
猜你喜欢
- 2024-09-26 神奇的pv命令--直观查看压缩和解压文件时的进度
- 2024-09-26 多线程之 AsyncTask 使用详解和从源码中深入理解 AsyncTask 机制
- 2024-09-26 Flutter 对 iOS、Android(双端开发者)的快速理解(二)
- 2024-09-26 Flutter项目如何将任务转移到后台线程?Flutter后台任务的写法
- 2024-09-26 精心整理:移动应用常见的Bug汇总及预防方法
- 2024-09-26 「技术」Qt对话框讲解(qt对话框关闭按钮禁灰)
- 2024-09-26 数据连接相关类简介(数据连接性问题)
- 2024-09-26 一行代码解决重复点击问题(重复一句话的代码)
- 2024-09-26 MUI框架原生wepApp启动遇到过的坑与总结
- 2024-09-26 wxPython - 进度对话框ProgressDialog
- 1515℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 579℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 515℃MySQL service启动脚本浅析(r12笔记第59天)
- 487℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 486℃启用MySQL查询缓存(mysql8.0查询缓存)
- 471℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 451℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 449℃MySQL server PID file could not be found!失败
- 最近发表
-
- 宝塔面板Nginx如何提高网站访问速度?
- 接口调试工具ApiPost中form-data/x-www-form-urlencoded/raw区别
- 高并发场景下,Nginx性能如何提升10倍?
- 高并发场景下,Nginx如何抗住千万级流量?
- 浏览器中在线预览pdf文件,pdf.mjs插件实现web预览pdf
- 为什么你的网站加载慢?90%的人忽略了这2个设置。
- 别再无脑复制Nginx配置了!掌握这10个"性能核弹"级参数
- 你的Nginx配置,可能就是你网站最慢的一环,注意这几个优化参数
- 深入浅出HTTP压缩技术(http2压缩)
- C程序设计之:1-1/2+1/3-... + 1/n 的和
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (83)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)