从OkHttp的使用谈谈封装网络库的思维过程
前言
????在Android开发中,网络请求是每个开发者的必备技能。当前也有很多优秀、开源的网络请求库。例如:
- OkHttp
- Retrofit
- Android-async-http
其中Retrofit是对OkHttp的封装,Android-async-http是对HttpClient的封装,利用这些网络库开发者可以极大提升编码效率。但是,即使有这么多优秀的网络库可以选择,大多数团队仍旧会自己封装网络库,为什么呢?所以本文首先就要探讨:
??1 为什么要封装自己的网络库呢?
????当知道封装网络库的必要性之后,便摩拳擦掌准备去大干一番,但很快便面对一个问题:
??2 如何一步步封装自己的网络库呢?
????终于封装好了自己的库,但使用时一定能发现不少bug和可以优化的地方,那么:
??3 应该用什么样的思想来指导改进网络库呢?
在答复上述问题之前,先理解下网络请求的基本流程:
网络请求基本流程
????网络请求的实质是去查看、修改远程计算机(包括服务器)上的信息,仅从用户端来看基本流程如下:
网络请求流程.png
????如图示,网络请求的基本流程就是如此简单,和把大象放入冰箱一样,都是三步。接下来我们在代码级别来看看:
如何进行网络请求
??以使用OkHttp框架访问百度首页为例子
//构造一个HttpClient 相当于设置个人邮箱。 OkHttpClient client=new OkHttpClient(); //创立Request 对象,相当于写信。 Request request = new Request.Builder() .url("http://www.baidu.com") .build() //将Request封装为call,相当于把信放进邮箱,成为设置后待发送的信件 Call call = client.newCall(request); // 放置到请求队列、开始发送并等待回复,相当于邮箱开始发动信件,并等待对方回复。 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // 当请求被取消、连接中断、找不到服务器等问题会调用这个接口 } @Override public void onResponse(Call call, final Response response) throws IOException { // 远程服务器成功返回调用 final String res = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { Log.e("TAG"," "+res); } }); } }); }
????由上可以看到用OkHttp框架进行网络请求逻辑清晰简单,跟我们用邮件跟朋友交流差不多。首先是设置邮箱(假如没有什么特殊需求就用默认设置、如上文)+写邮件内容,而后把写好邮件后放到设置好的邮箱里面,最后点击发送,等待朋友的回复,当朋友回复了就去查看解决。
为什么要封装网络库
????知道如何用OkHttp后很兴奋,于是用这一套开始了网络请求之旅,so easy!!复制-粘贴-修改,复制-粘贴-修改,复制-粘贴-修改…终于做了七八个网络请求,一看任务量完成了六分之一,啊–累死宝宝了!!
????不行了不行了,要喝杯luckin coffee鼓舞下士气,说走就走,喝着咖啡想着回来就一口气加班搞定它。忽然想起OOP重要准则-代码复用准则,一拍脑子我TM真是个憨货,把这些请求的共同部分提取出来,对外提供更简单的接口、这样用着方便不容易出错、以后有问题修改工作量也大大减少了,这就是我们封装网络库第一个起因:
????一、近似业务模型的代码复用–方便使用和修改;
????说干就干,撸起袖子正准备上场,再次灵光一闪,不对不对,这次要好好思考下整个完美的,半途而废啥的最白费时间了!首先看看网络上优秀的开源库都有哪些功能可以借鉴。
????去github上看了star比较多的少量OkHttp库封装,整理了下功能清单:
- 一般的get请求
- 一般的post请求
- 基于Http Post的文件上传(相似表单)
- 文件下载/加载图片
- 上传下载的进度回调
- 支持取消某个请求
- 支持自己设置Callback
- 支持HEAD、DELETE、PATCH、PUT
- 支持session的保持
- 支持自签名网站https的访问,提供方法设置下证书就行
- 支持RxJava
- 支持自己设置缓存策略
????这些网络库是很好参考借鉴,但是,它们跟我们的业务结合不紧密、直接用还是会造成:
????1 每个网络请求都要加上业务逻辑;
????2 有不少多余功能,导致网络库很大,可能影响效率 ;
????3 团队特殊要求达不到,比方利用三方实现DNS防劫持。
????所以需要仔细去分析业务、梳理网络请求类型。很快我们发现需要四种缓存策略:立即请求、缓存10s、缓存1h、缓存24h,所以封装网络库第二个起因也是功能点:
????二、 全局,全团队统一的缓存策略;
????接下来需要去沉下心,去谷歌搜索下网络请求问题,针对业务场景去思考下一旦上线会遇到什么样的问题,很快确定了第二个问题,DNS劫持问题,所以我们封装网路库第二个起因也是重要功能点:
????三、 全局、团队统一的 DNS反劫持;
????为了防止遗漏,又去找团队成员、上级老大聊天请教,看有什么特殊要求。这时候经营部门提了个需求,统计dns劫持率,老大说出现网络问题要能够快速定位。所以封装网络库第四个重点:
????四、 全局、团队统一的网络请求统计和关键log
????明确了什么要封装自己的网络库,以及必需具有哪些功能,接下便开始正面遭遇问题:
如何一步步封装网络库呢
????人类做事习惯上是顺序进行的,这就决定了人类的可靠性思维-逻辑思维是线性的,进而决定了人类的可靠性表达也是线性的!越顺滑的思路,越顺滑的表达,越容易被人了解与接受。
????所以当遭遇事件类相关任务,又不知道怎样做的时候,从事件整体业务流程进行分析是一个很好的切入点。
????从整体业务流程出发、使我们不至于迷失,但到了每一个环节该如何做,就需要少量指导与规范,那必需就是:
????SDK设计准则: A 简洁易用 B 功能完备 C 扩展性好。
????当然 “简单吗?柔美吗?” 也是每个软件工程师须时时反问的。
一 简洁易用-从客户使用与了解角度考虑对外接口与板块划分
????现在假设网络库已经写好了,客户该库发起网络请求,所以遇到第一个问题节点就是网络库对外接口设计。接口设计准则是简单!简单!简单!不仅仅是代码看着的简单,而是在于客户易于了解和使用的简单!
????上文提到通过OkHttp网络请求大概分为四步:
????1 构建、设置OkHttpClient–相当于设置电子邮箱;
????2 构建Request请求内容–相当于写信;
????3 用OkHttpClient把Request转换成为待发送的Request-Call–相当于把信件放入邮箱,变为邮件;
????4 发送请求,等待回复,并解决。
????现在就设想最简单的网络请求是怎样样的:额、大概是这样的吧—-用户端增加一个请求(由Url构建出来)- 发送出去-等待回复解决,比方如下
new HttpClient.HttpClientBuilder().build() // 设置邮箱 .addRequest(new GetRequest("http://www.baidu.com")) //写信并增加到邮箱或者者叫写邮件 .sendRequest(new DefaultCallback(){ //发送邮件等待回复 @Override public void onResponse(Call call, Response response) { super.onResponse(call, response); Log.e("JG","response="+response.toString()); //服务器成功返回 } @Override public void onFailure(Call call, IOException e) { super.onFailure(call, e); } });
????从这个简单流程出发、网络库可笼统出如下板块:
????1 HttpClient 用户端板块 ;
????2 Request 请求板块 ;
????3 Callback 回复解决模。
????据此我们可以把网络库板块划分为这三个大的板块。同理,在每一个大板块内部也要根据流程进行划分为更细的板块
????这一小节主要讨论板块设计与划分,核心思想是跳出代码逻辑,从整体业务流程出发,找到关键的解决节点,从而对网路库进行板块设计与划分。但究竟这种设计能否行得通,还要从每一个具体业务实现进行重新审核。
功能完备-从业务需求实现出发审查板块划分的正当性
????接下来我们开始分析每一个业务需求、验证刚才的板块划分能否正当。
1 get/post请求
????最初我们从get/post请求开始思考业务逻辑,所以这个可以跟板块完美结合,get与post的区别在我们这里就是不同的Request封装。
2 DNS防劫持
????DNS-Domin Name System域名解析系统,将域名(例如:“http://www.baidu.com”)转换为IP地址(例如:220.181.112.244),这个解析过程涉及到本地缓存,经营商缓存,各级别域名服务器等,它是Http协议的一部分。
????DNS劫持劫持又称域名劫持,本质就是通过攻破DNS解析过程中某些环节与节点,来给客户返回假网址IP。
????既然DNS劫持结果是返回错误的IP,那能否直接用Ip来访问即可以防止DNS劫持了?这就是DNS反劫持的主要思想:拿到域名->来通过Http请求->访问权威三方(比方阿里的HTTPDNS)提供的DNS解析服务器->三方告诉你IP,便可通过该IP来进行网络请求了。
????OkHttp实现DNS反劫持:由上文可知看到DNS反劫持关键在于,用三方的DNS解析系统代替系统默认的DNS解析系统!所以OkHttp提供了一个笼统的DNS类,客户只用继承这个类,便可以方便的接入自己设置的DNS解析系统。
public class HttpDns implements Dns { private static final String TAG = HttpDns.class.getSimpleName(); @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { Log.v(TAG, "lookup:" + hostname); //只要要在lookup方法中调用HttpDns的SDK去获取IP // 假如获取到了就返回一个List<InetAddress>的值 // 假如购买了阿里的HttpDns服务即可以用 //默认又返回系统的DNS解析,这就叫DNS降级 return SYSTEM.lookup(hostname); }}
而后通过OkHttClient设置此DNS,HttpClient板块主要就是封装OkHttClient,所以审核通过!可行再次+1。
3 缓存设置
????缓存设置指缓存的位置、大小、时间,OkHttp通过两种方式可以实现缓存设置:
//1 通过库cache接口new OkHttpClient.Builder() .cache(new Cache(file, cacheSize)) // 配置缓存// 2 通过阻拦器new OkHttpClient.Builder() .cache(new CacheInterceptor(){ @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = chain.proceed(request); return response; } });
具体做法请参照:okhttp 缓存实践
它仍旧可以通过OkHttpClient完成,我们仍旧只要要把这部分封装在HttpClient板块就好。
4 全局log统计
????全局log统计仍旧是使用阻拦器完成,上代码
public class LogInterceptor implements Interceptor { private static final String TAG=LogInterceptor.class.getSimpleName(); @Override public Response intercept(Chain chain) throws IOException { // 把请求request阻拦下来 Request request = chain.request(); //可以打印请求内容 Log.v(TAG,"request method="+request.method()+",request url="+request.url()); //继续向下一个传递解决,并阻拦四处理结果response Response response = chain.proceed(request); // 可以打印返回内容 Log.v(TAG,"response="+response.toString()); return response; }}
仍旧是封装在HttpClient板块。一律审核通过,不过从最初设计也可以知道,我们从OkHttp出借鉴思想,一定是可行的。
三、 扩展性好-从开闭准则进行板块间解耦操作
????现在各个板块分工明确、业务功能基本一律实现了,但随着业务的发展、我们可能会有新类型的请求、新种类的返回解决等,所以一开始我们就要考虑整个库的扩展性。
????扩展性好的关键在于板块间耦合度低,解耦的关键在于依赖笼统,也就是实体板块(比方一个实体类)之间没有直接调用关系,实体板块之间数据传递要通过中间层(笼统类或者者接口)。
再次回顾下我们最初设计的调用接口:
new HttpClient.HttpClientBuilder().build() // 设置邮箱 .addRequest(new GetRequest("http://www.baidu.com")) //写信并增加到邮箱或者者叫写邮件 .sendRequest(new DefaultCallback(){ //发送邮件等待回复 @Override public void onResponse(Call call, Response response) { super.onResponse(call, response); Log.e("JG","response="+response.toString()); //服务器成功返回 } @Override public void onFailure(Call call, IOException e) { super.onFailure(call, e); } });
其中 addRequest
public ReadyRequest addRequest(BaseRequest baseRequest){ return new ReadyRequest(this,baseRequest); }
其中GetRequest是继承自BaseRequest,这样HttpClient这个实体类就没有直接和实体类GetRequest相关,这就是通过依赖笼统进行理解耦合。当我们需要一种新的Request,只要继承BaseRequest即可以方便的扩展使用。
如何不断优化网络库
????现在我们做好了一个基本可以使用、并且具有肯定扩展性的网络库,但是使用过程中一定可以发现bug和可以优化地方,那么如何一步步把我们的网络库从普通变为卓越呢?
????那首先我还是会问一个问题,对一个具体APP来说怎样样才是一个顶级的网络库?
????1 安全性高;
????2 网络访问速度快、性能优越;
????3 客户使用方便。
看了少量优秀的网络库改进过程,暂时总结出来点如下:
1 统计网络请求常见bug与风险,添加预防解决;
2 统计业务流程想关性,进行预加载或者者缓存;
3 统计客户使用习惯和思维、统一智可设置或者者修改接口;
4 不断讨论学习其余优秀的软件设计思维与思维,进行部分重构。
这些都是大数据与AI思维的延伸,这部分还在继续思考中,假如各位有什么想法、欢迎在下面交流评论!!
总结
????本文从简单的网络请求开始,谈到了为了业务需求和方便使用来封装自己的网络库,进而讨论了如何一步步封装一个网络库,并不断优化、完善。其中重点是想重现了下封装、优化SDK的一个思维过程:
????1 跳出代码,从整体业务流程进行初步板块划分;
????2 从方便(客户)了解使用的角度设计调用接口;
????3 从业务具体实现出发,重新审视板块划分;
????4 用软件设计思维与模式再次审视当前结构设计,添加扩展性、方便客户灵活扩展。
????5 用大数据与AI进化思维进行不停优化;
????同时,也花了一两天时间,手动封装了一个网络库来验证思维过程的可行性。gitHub地址: kingkong-li/networklib
欢迎各位大神前来交流、共同开发学习~~
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 从OkHttp的使用谈谈封装网络库的思维过程