模拟OkHttp,手写简易版网络访问框架

网友投稿 567 2022-10-27

模拟OkHttp,手写简易版网络访问框架

模拟OkHttp,手写简易版网络访问框架

开篇废话

趁着周末两天的时间,跟着大神的脚步,把我们经常使用的网络框架OkHttp的源码好好跟了一下,初次观看,确实非常容易钻进去,搞得云里雾里,在大神的指导下,才勉强把整个逻辑走通。写这篇文章,也是希望自己脑袋里面,能对这个网络框架有一个整体的认识,了解它整体设计思想。

大概了解后,还是需要自己亲自动手,来手写当中的一些细节,加深自己理解,所以,接下来,我会给出OkHttp中设计的主线,以及模拟OkHttp,手写一个属于我们自己的网络框架。

废话就到此为止了,开始这次的学习之旅吧。。。

技术详情

1. OkHttp 的主线调用

关于OkHttp的使用,我这里就不说了,不清楚的可以,在简书里面搜索一下,应该有非常多文章来说明,我这里就大概说一下调用的主线流程。

OkHttp中-的整体调用逻辑,可以查看下面这张图,可以说非常清晰了,细节地方,自己可以去查看源码了:

对于当中具体调用细节,我这里就不再讲述了,可以根据手写的简易OkHttp框架来对它们有一个整体的认识。

2. 模拟OkHttp,手写简易Http框架

对OkHttp的设计思路有一个整体的认识后,自己手写一个简易版的Http框架,加深一下对OkHttp源码的认识。手写Http框架,当中能够学到以下知识点:

1.对于http协议能够有熟悉的认识 2.线程池的项目实践 3.建造者模式的项目实践 4.socket层的字节流处理 5.责任链模式的项目实践,熟悉OkHttp中的责任链模式的-

首先,根据使用者习惯,完成HttpClient的功能(建造者模式):

使用者能够通过设置失败重试次数,自定义-构造一个httpClient对象

得到HttpClient对象后,就能拿到Call对象了,以下是Call里面的实现:

public class Call { private HttpClient httpClient; private Request request; public Request getRequest() { return request; } public HttpClient getHttpClient() { return httpClient; } //TODO 是否被执行过 boolean executed; //TODO 是否被取消了 boolean canceled; public boolean isCanceled() { return canceled; } public Call(HttpClient httpClient, Request request){ this.httpClient = httpClient; this.request = request; } /** * 获取返回 * @return * @throws IOException */ Response getResponse() throws IOException{ ArrayList interceptors = new ArrayList<>(); interceptors.addAll(httpClient.getInterceptors()); interceptors.add(new RetryInterceptor()); interceptors.add(new HeadersInterceptor()); interceptors.add(new ConnectionInterceptor()); interceptors.add(new CallServiceInterceptor()); InterceptorChain interceptorChain = new InterceptorChain(interceptors,0,this,null); Response response = interceptorChain.proceed(); return response; } /** * 将Call对象放到调度器里面去执行,如果已经加过了,就不能加了 * @param callback * @return */ public Call enqueue(Callback callback){ synchronized (this){ if(executed){ throw new IllegalStateException("This Call Already Executed!"); } executed = true; } httpClient.getDispather().enqueue(new AsyncCall(callback)); return this; } final class AsyncCall implements Runnable{ private Callback callback; public AsyncCall(Callback callback){ this.callback = callback; } @Override public void run() { boolean signalledCallback = false; try { Response response = getResponse(); if(canceled){ signalledCallback = true; callback.onFailure(Call.this,new IOException("this task had canceled")); }else{ signalledCallback = true; callback.onResponse(Call.this,response); } } catch (IOException e) { if(!signalledCallback){ callback.onFailure(Call.this,e); } } finally { //将这个任务从调度器移除 httpClient.getDispather().finished(this); } } public String getHost(){ return request.getHttpUrl().getHost(); } }}

Call类中的AsyncCall线程,是请求真正开始的地方,使用者调用enqueue的时候,只是把这个AsyncCall线程添加到调度器Dispather的线程池,其中,Dispacher中的实现如下:

调度器中只是负责把添加进来的请求进行按序执行管理,真正执行,还是在AsyncCall线程中run方法,其中的责任链式的-,也在这里面进行添加,执行。 我这里自己手写的-,没有像OkHttp那么全而细,知道它的设计思想后,自己只是手动实现一个简单的责任链-,其中包括

1.重试-2.Http头-3.选择有效socket连接的-4.socket通信-

这个顺序是不能随意调换的,就跟工厂里面的流水线一样,一步一步往下走的。 首先,实现的是重试-

public class RetryInterceptor implements Interceptor { @Override public Response intercept(InterceptorChain interceptorChain) throws IOException { Log.e("interceprot", "重试-...."); Call call = interceptorChain.call; IOException ioException = null; for(int i = 0 ; i < call.getHttpClient().getRetryTimes(); i ++){ if(call.isCanceled()){ throw new IOException("this task had canceled"); } try { Response response = interceptorChain.proceed(); return response; }catch (IOException e){ ioException = e; } } throw ioException; }}

然后是Http头处理的-

public class HeadersInterceptor implements Interceptor { @Override public Response intercept(InterceptorChain interceptorChain) throws IOException { Log.e("interceprot","Http头-...."); Request request = interceptorChain.call.getRequest(); Map headers = request.getHeaders(); if(!headers.containsKey(HttpCodec.HEAD_HOST)){ headers.put(HttpCodec.HEAD_HOST,request.getHttpUrl().getHost()); } if(!headers.containsKey(HttpCodec.HEAD_CONNECTION)) { headers.put(HttpCodec.HEAD_CONNECTION, HttpCodec.HEAD_VALUE_KEEP_ALIVE); } if(null != request.getRequestBody()){ String contentType = request.getRequestBody().getContentType(); if(null != contentType){ headers.put(HttpCodec.HEAD_CONTENT_TYPE,contentType); } long contentLength = request.getRequestBody().getContentLength(); if(-1 != contentLength){ headers.put(HttpCodec.HEAD_CONTENT_LENGTH,Long.toString(contentLength)); } } return interceptorChain.proceed(); }}

接着是选择可用socket连接的-

public class ConnectionInterceptor implements Interceptor { @Override public Response intercept(InterceptorChain interceptorChain) throws IOException { Log.e("interceptor", "获取连接-"); Request request = interceptorChain.call.getRequest(); HttpClient httpClient = interceptorChain.call.getHttpClient(); HttpUrl httpUrl = request.getHttpUrl(); HttpConnection httpConnection = httpClient.getConnectionPool().getHttpConnection(httpUrl.getHost(),httpUrl.getPort()); if(null == httpConnection){ httpConnection = new HttpConnection(); }else{ Log.e("interceptor", "从连接池中获得连接"); } httpConnection.setRequest(request); try { Response response = interceptorChain.proceed(httpConnection); if (response.isKeepAlive()){ httpClient.getConnectionPool().putHttpConnection(httpConnection); }else{ httpConnection.close(); } return response; }catch (IOException e){ httpConnection.close(); throw e; } }}

把请求的配置信息都配置好后,最后,交给socket去通信,去解析,就是socket通信-了:

public class CallServiceInterceptor implements Interceptor { @Override public Response intercept(InterceptorChain interceptorChain) throws IOException { Log.e("interceptor", "通信-"); HttpConnection httpConnection = interceptorChain.httpConnection; HttpCodec httpCodec = new HttpCodec(); InputStream inputStream = httpConnection.call(httpCodec); //获取服务器返回的响应行 HTTP/1.1 200 OK\r\n String statusLine = httpCodec.readLine(inputStream); //获取服务器返回的响应头 Map headers = httpCodec.readHeaders(inputStream); //根据Content-Length或者Transfer-Encoding(分块)计算响应体的长度 int contentLength = -1; if(headers.containsKey(HttpCodec.HEAD_CONTENT_LENGTH)){ contentLength = Integer.valueOf(headers.get(HttpCodec.HEAD_CONTENT_LENGTH)); } //是否为分块编码 boolean isChunked = false; if(headers.containsKey(HttpCodec.HEAD_TRANSFER_ENCODING)){ isChunked = headers.get(HttpCodec.HEAD_TRANSFER_ENCODING).equalsIgnoreCase(HttpCodec.HEAD_VALUE_CHUNKED); } //获取服务器响应体 String body = null; if(contentLength > 0){ byte[] bodyBytes = httpCodec.readBytes(inputStream,contentLength); body = new String(bodyBytes,HttpCodec.ENCODE); }else if(isChunked){ body = httpCodec.readChunked(inputStream,contentLength); } // HTTP/1.1 200 OK\r\n status[0] = "HTTP/1.1",status[1] = "200",status[2] = "OK\r\n" String[] status = statusLine.split(" "); //根据响应头中的Connection的值,来判断是否能够复用连接 boolean isKeepAlive = false; if(headers.containsKey(HttpCodec.HEAD_CONNECTION)){ isKeepAlive = headers.get(HttpCodec.HEAD_CONNECTION).equalsIgnoreCase(HttpCodec.HEAD_VALUE_KEEP_ALIVE); } //更新此请求的最新使用时间,作用于线程池的清理工作 httpConnection.updateLastUseTime(); return new Response(Integer.valueOf(status[1]),contentLength,headers,body,isKeepAlive); }}

经过以上四个-后,使用者就能够正常使用http或者https的请求了。这里面还有一些额外处理的类,我这里就没有给出来了,具体的可以去我的github上查看项目源码,注释写了蛮多,应该很好理解。

关于此项目的源代码,在文章最后,会提供github地址,欢迎star

干货总结

阅读框架源码,说真的,确实是一件非常蛋疼的事,但是为了提升自己,却不得不去啃这个硬骨头,学习他人优秀的设计思想为己所用。只有积累的足够多方法之后,我们才能真正得去创造。

对于我们项目中框架的使用,会引入很多不一样的框架,比如retrofit(okhttp),glide,greenDao数据库框架,arouter路由框架,rxjava,butterknife,以及一些其他的第三方自定义控件等,就会不知不觉是我们的项目越发的庞大,而且比较难以维护,所以,建议我们自己项目,能够尽量自己手写相关的框架,而不要直接引用别人的(我个人的愿景,不喜勿喷)。

希望通过此文章以及源码,能够学习到以下知识点,也不枉我码了那么多字:

1.对于http协议能够有熟悉的认识 2.线程池的项目实践 3.建造者模式的项目实践 4.socket层的字节流处理 5.责任链模式的项目实践,熟悉OkHttp中的责任链模式的-

接下来的日子,希望能够对数据库框架,路由框架,图片加载框架,换肤框架,热更新框架等框架,进行熟悉,然后给出自己手写的简易版本,提升自身对于这些框架的认识。

以下是我的博客地址:

项目的简书地址

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:原因分析IDEA导入Spring
下一篇:Droid-FF是一个可扩展的模糊框架Android
相关文章

 发表评论

暂时没有评论,来抢沙发吧~