我们先来回忆一下Volley的用法,使用Volley前,我们一般会先构造出一个RequestQueue,然后不断往该对象中添加请求Request,之后Volley便会进行调度,至于走缓存还是走网络这就看就没有请求过了。而构造RequestQueue的方法如下。
Volley.newRequestQueue(mContext);
而该方法,内部实际上是调用了两个参数的重载方法
public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); }
我们看下两个参数的重载方法的实现。
private static final String DEFAULT_CACHE_DIR = "volley"; public static RequestQueue newRequestQueue(Context context, HttpStack stack) { File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } Network network = new BasicNetwork(stack); RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; }
在该方法中,进行了以下几步的操作
- 创建缓存目录,即第一行代码实现的内容。
- 获得了userAgent,默认情况下是volley/0,如果能够获取到包名,则会被重写为包名/版本号
- 然后根据第二个入参HttpStack,判断是否为空,其实我们默认的构造RequestQueue中就是传了一个null,如果不为null,则使用参数,如果为null,则根据android系统的版本进行判断,如果系统版本大于等于9,则使用HurlStack,如果系统版本小于9,则使用HttpClientStack
- 之后构造Network和Cache对象,将它们传入RequestQueue的构造函数中构造RequestQueue对象,调用该对象的start方法开始死循环,在该循环中如果消息队列中有新的请求则会进行处理。
在这个过程中我们需要关心的是HurlStack和HttpClientStack到底是什么。其实这两个对象都是实现了HttpStack接口的。该接口的定义如下。
public interface HttpStack { public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError;}
该方法中的逻辑需要实现根据参数去完成一个请求,并返回请求结果。我们需要根据这两个实现类,去仿一个OkHttp的实现类,首先我们看一下HurlStack的实现。也就是通过HttpUrlConnection实现的
@Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl(); HashMap<String, String> map = new HashMap<String, String>(); map.putAll(request.getHeaders()); map.putAll(additionalHeaders); if (mUrlRewriter != null) { String rewritten = mUrlRewriter.rewriteUrl(url); if (rewritten == null) { throw new IOException("URL blocked by rewriter: " + url); } url = rewritten; } URL parsedUrl = new URL(url); HttpURLConnection connection = openConnection(parsedUrl, request); for (String headerName : map.keySet()) { connection.addRequestProperty(headerName, map.get(headerName)); } setConnectionParametersForRequest(connection, request); // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); int responseCode = connection.getResponseCode(); if (responseCode == -1) { // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) { response.setEntity(entityFromConnection(connection)); } for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response; }
首先拿到url,新建了一个Map对象,往该map对象中放入了请求头的内容,请求头来自两个地方,一个是入参Request中,通过request.getHeaders()获得,一个是入参additionalHeaders中,直接add进去即可
然后会判断一个叫mUrlRewriter的变量是否为空,如果不为空,则调用它的rewriteUrl方法返回新的url,其实它是一个接口,其定义如下,默认情况下这个变量是空,所以这一过程不会被调用。
public interface UrlRewriter { /** * Returns a URL to use instead of the provided one, or null to indicate * this URL should not be used at all. */ public String rewriteUrl(String originalUrl); }
- 根据url创建URL对象
- 通过openConnection方法获得了一个HttpURLConnection对象
protected HttpURLConnection createConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection();}private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { HttpURLConnection connection = createConnection(url); int timeoutMs = request.getTimeoutMs(); connection.setConnectTimeout(timeoutMs); connection.setReadTimeout(timeoutMs); connection.setUseCaches(false); connection.setDoInput(true); // use caller-provided custom SslSocketFactory, if any, for HTTPS if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); } return connection;}
在该方法中的操作,主要是打开一个连接,设置超时时间,SSLSocketFactory,是否使用缓存等内容。
然后通过一个for循环往获得的HttpsURLConnection对象中添加请求头,也就是刚才的map
调用setConnectionParametersForRequest设置请求方法和请求体
static void setConnectionParametersForRequest(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: // This is the deprecated way that needs to be handled for backwards compatibility. // If the request's post body is null, then the assumption is that the request is // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); if (postBody != null) { // Prepare output. There is no need to set Content-Length explicitly, // since this is handled by HttpURLConnection using the size of the prepared // output stream. connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(postBody); out.close(); } break; case Method.GET: // Not necessary to set the request method because connection defaults to GET but // being explicit here. connection.setRequestMethod("GET"); break; case Method.DELETE: connection.setRequestMethod("DELETE"); break; case Method.POST: connection.setRequestMethod("POST"); addBodyIfExists(connection, request); break; case Method.PUT: connection.setRequestMethod("PUT"); addBodyIfExists(connection, request); break; case Method.HEAD: connection.setRequestMethod("HEAD"); break; case Method.OPTIONS: connection.setRequestMethod("OPTIONS"); break; case Method.TRACE: connection.setRequestMethod("TRACE"); break; case Method.PATCH: connection.setRequestMethod("PATCH"); addBodyIfExists(connection, request); break; default: throw new IllegalStateException("Unknown method type."); } } private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { byte[] body = request.getBody(); if (body != null) { connection.setDoOutput(true); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(body); out.close(); } }
该方法会会根据请求的方法调用setRequestMethod设置对应的字符串,并且如果是POST,PUT,PATCH方法,则会调用addBodyIfExists方法设置请求体
- 之后是新建一个ProtocolVersion对象,传的是HTTP 1.1
- 获得响应码,如果为-1则扔出异常
- 构造响应状态行StatusLine
- 构造响应体,会根据hasResponseBody函数判断是否有响应体内容,如果有则进行设置。
private static boolean hasResponseBody(int requestMethod, int responseCode) { return requestMethod != Request.Method.HEAD && !(HttpStatus.SC_CONTINUE <= responseCode && responseCode < HttpStatus.SC_OK) && responseCode != HttpStatus.SC_NO_CONTENT && responseCode != HttpStatus.SC_NOT_MODIFIED;}private static HttpEntity entityFromConnection(HttpURLConnection connection) { BasicHttpEntity entity = new BasicHttpEntity(); InputStream inputStream; try { inputStream = connection.getInputStream(); } catch (IOException ioe) { inputStream = connection.getErrorStream(); } entity.setContent(inputStream); entity.setContentLength(connection.getContentLength()); entity.setContentEncoding(connection.getContentEncoding()); entity.setContentType(connection.getContentType()); return entity;}
- 之后将响应头添加到HttpResponse对象中去,并返回这个对象
整个过程其实也是相当简单的,接下来看HttpClientStack的实现,该实现和HurlStack十分相似。
@Overridepublic HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders); addHeaders(httpRequest, additionalHeaders); addHeaders(httpRequest, request.getHeaders()); onPrepareRequest(httpRequest); HttpParams httpParams = httpRequest.getParams(); int timeoutMs = request.getTimeoutMs(); // TODO: Reevaluate this connection timeout based on more wide-scale // data collection and possibly different for wifi vs. 3G. HttpConnectionParams.setConnectionTimeout(httpParams, 5000); HttpConnectionParams.setSoTimeout(httpParams, timeoutMs); return mClient.execute(httpRequest);}
- 首先调用createHttpRequest方法设置请求的内容
static HttpUriRequest createHttpRequest(Request<?> request, Map<String, String> additionalHeaders) throws AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: { // This is the deprecated way that needs to be handled for backwards compatibility. // If the request's post body is null, then the assumption is that the request is // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); if (postBody != null) { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); HttpEntity entity; entity = new ByteArrayEntity(postBody); postRequest.setEntity(entity); return postRequest; } else { return new HttpGet(request.getUrl()); } } case Method.GET: return new HttpGet(request.getUrl()); case Method.DELETE: return new HttpDelete(request.getUrl()); case Method.POST: { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(postRequest, request); return postRequest; } case Method.PUT: { HttpPut putRequest = new HttpPut(request.getUrl()); putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(putRequest, request); return putRequest; } case Method.HEAD: return new HttpHead(request.getUrl()); case Method.OPTIONS: return new HttpOptions(request.getUrl()); case Method.TRACE: return new HttpTrace(request.getUrl()); case Method.PATCH: { HttpPatch patchRequest = new HttpPatch(request.getUrl()); patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(patchRequest, request); return patchRequest; } default: throw new IllegalStateException("Unknown request method."); } }private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest, Request<?> request) throws AuthFailureError { byte[] body = request.getBody(); if (body != null) { HttpEntity entity = new ByteArrayEntity(body); httpRequest.setEntity(entity); }}
这一步,和HurlStack中的setConnectionParametersForRequest的作用是类似的
- 根据上一步的返回结果HttpUriRequest 对象,调用addHeaders对该对象设置请求头,请求头的来源和HurlStack是一样的
private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) { for (String key : headers.keySet()) { httpRequest.setHeader(key, headers.get(key)); }}
- 设置超时时间
- 然后调用HttpClient对象的execute方法,获得HttpResponse对象
可以从上面两个类中看到很多类似的地方
- 设置请求头
- 设置请求方法和请求体
- 设置超时时间等参数
- 设置响应头和响应体
根据这些步骤,我们写一个使用OkHttp实现的HttpStack的实现类。首先创建类OkHttpStack,实现HttpStack接口
public class OkHttpStack implements HttpStack { @Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { return null; }}
提供一个构造函数,传入OkHttpClient 对象
private final OkHttpClient mOkHttpClient;public OkHttpStack(OkHttpClient okHttpClient) { if (okHttpClient == null) { throw new IllegalArgumentException("OkHttpClient can't be null"); } mOkHttpClient = okHttpClient;}
然后实现performRequest方法,首先我们设置一下超时时间
OkHttpClient client = mOkHttpClient.clone();int timeoutMs = request.getTimeoutMs();client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS);client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS);client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS);
为了不对外部的OkHttpClient 造成影响,我们这里调用了clone方法克隆了一个。当然也可以直接使用原对象,即
OkHttpClient client = mOkHttpClient;int timeoutMs = request.getTimeoutMs();client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS);client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS);client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS);
接下来设置请求头,请求头的来源有两个,上面已经解析过了。
com.squareup.okhttp.Request.Builder builder=new com.squareup.okhttp.Request.Builder();builder.url(request.getUrl());Map<String,String> headers=request.getHeaders();for(String name:headers.keySet()){ builder.addHeader(name,headers.get(name));}for(String name:additionalHeaders.keySet()){ builder.addHeader(name,additionalHeaders.get(name));}
设置请求头完毕后自然就是请求方法和请求体了,为了保持命名的约定,还是使用setConnectionParametersForRequest函数名,其实现如下
static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Request.Method.DEPRECATED_GET_OR_POST: byte[] postBody = request.getPostBody(); if (postBody != null) { builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()),postBody)); } break; case Request.Method.GET: builder.get(); break; case Request.Method.DELETE: builder.delete(); break; case Request.Method.POST: builder.post(createRequestBody(request)); break; case Request.Method.PUT: builder.put(createRequestBody(request)); break; case Request.Method.HEAD: builder.head(); break; case Request.Method.OPTIONS: builder.method("OPTIONS",null); break; case Request.Method.TRACE: builder.method("TRACE",null); break; case Request.Method.PATCH: builder.patch(createRequestBody(request)); break; default: throw new IllegalStateException("Unknown method type."); }}
内部还调用了createRequestBody方法设置请求体,其实现如下
private static RequestBody createRequestBody(Request r) throws AuthFailureError{ final byte[] body = r.getBody(); if (body == null) return null; return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);}
设置完毕后,自然就是开始请求了
com.squareup.okhttp.Request okRequest=builder.build();Call call=client.newCall(okRequest);Response okresponse=call.execute();
请求完毕,对响应头和响应体进行设置。
首先获得请求的协议
private static ProtocolVersion parseProtocol(final Protocol p){ switch (p) { case HTTP_1_0: return new ProtocolVersion("HTTP", 1, 0); case HTTP_1_1: return new ProtocolVersion("HTTP", 1, 1); case SPDY_3: return new ProtocolVersion("SPDY", 3, 1); case HTTP_2: return new ProtocolVersion("HTTP", 2, 0); } throw new IllegalAccessError("Unkwown protocol");}
构造响应状态行
BasicStatusLine responseStatus = new BasicStatusLine( parseProtocol(okresponse.protocol()), okresponse.code(), okresponse.message() );
设置响应体
BasicHttpResponse response=new BasicHttpResponse(responseStatus);response.setEntity(entityFromOkHttpResponse(okresponse));
entityFromOkHttpResponse函数的实现如下,包括对响应内容,响应内容长度,响应内容编码,响应类型进行设置
private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException{ BasicHttpEntity entity = new BasicHttpEntity(); ResponseBody body = r.body(); entity.setContent(body.byteStream()); entity.setContentLength(body.contentLength()); entity.setContentEncoding(r.header("Content-Encoding")); if (body.contentType() != null) { entity.setContentType(body.contentType().type()); } return entity;}
设置响应头
int size=responseHeaders.size();String name=null;String value=null;for(int i=0;i<size;i++){ name=responseHeaders.name(i); if (name!=null){ response.addHeader(new BasicHeader(name,value)); }}
最后返回HttpResponse对象
return response;
贴一下全部的代码
public class OkHttpStack implements HttpStack { private final OkHttpClient mOkHttpClient; public OkHttpStack(OkHttpClient okHttpClient) { if (okHttpClient == null) { throw new IllegalArgumentException("OkHttpClient can't be null"); } mOkHttpClient = okHttpClient; } @Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { OkHttpClient client = mOkHttpClient; int timeoutMs = request.getTimeoutMs(); client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS); client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS); client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS); com.squareup.okhttp.Request.Builder builder = new com.squareup.okhttp.Request.Builder(); builder.url(request.getUrl()); Map<String, String> headers = request.getHeaders(); for (String name : headers.keySet()) { builder.addHeader(name, headers.get(name)); } for (String name : additionalHeaders.keySet()) { builder.addHeader(name, additionalHeaders.get(name)); } setConnectionParametersForRequest(builder, request); com.squareup.okhttp.Request okRequest = builder.build(); Call call = client.newCall(okRequest); Response okresponse = call.execute(); BasicStatusLine responseStatus = new BasicStatusLine( parseProtocol(okresponse.protocol()), okresponse.code(), okresponse.message() ); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromOkHttpResponse(okresponse)); Headers responseHeaders = okresponse.headers(); int size = responseHeaders.size(); String name = null; String value = null; for (int i = 0; i < size; i++) { name = responseHeaders.name(i); if (name != null) { response.addHeader(new BasicHeader(name, value)); } } return response; } private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException { BasicHttpEntity entity = new BasicHttpEntity(); ResponseBody body = r.body(); entity.setContent(body.byteStream()); entity.setContentLength(body.contentLength()); entity.setContentEncoding(r.header("Content-Encoding")); if (body.contentType() != null) { entity.setContentType(body.contentType().type()); } return entity; } static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Request.Method.DEPRECATED_GET_OR_POST: byte[] postBody = request.getPostBody(); if (postBody != null) { builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()), postBody)); } break; case Request.Method.GET: builder.get(); break; case Request.Method.DELETE: builder.delete(); break; case Request.Method.POST: builder.post(createRequestBody(request)); break; case Request.Method.PUT: builder.put(createRequestBody(request)); break; case Request.Method.HEAD: builder.head(); break; case Request.Method.OPTIONS: builder.method("OPTIONS", null); break; case Request.Method.TRACE: builder.method("TRACE", null); break; case Request.Method.PATCH: builder.patch(createRequestBody(request)); break; default: throw new IllegalStateException("Unknown method type."); } } private static ProtocolVersion parseProtocol(final Protocol p) { switch (p) { case HTTP_1_0: return new ProtocolVersion("HTTP", 1, 0); case HTTP_1_1: return new ProtocolVersion("HTTP", 1, 1); case SPDY_3: return new ProtocolVersion("SPDY", 3, 1); case HTTP_2: return new ProtocolVersion("HTTP", 2, 0); } throw new IllegalAccessError("Unkwown protocol"); } private static RequestBody createRequestBody(Request r) throws AuthFailureError { final byte[] body = r.getBody(); if (body == null) return null; return RequestBody.create(MediaType.parse(r.getBodyContentType()), body); }}
至于用法,也很简单,在创建RequestQueue对象的时候,第二个参数传入我们的OkHttpStack对象即可。
Volley.newRequestQueue(this,new OkHttpStack(new OkHttpClient()));
此外,网上还有一种更加简洁的实现,但是得添加okhttp-urlconnection依赖
compile 'com.squareup.okhttp:okhttp:2.5.0'compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0'
该实现不是通过实现HttpStack接口实现的,而是继承HurlStack类实现的
public class SimpleOkHttpStack extends HurlStack { private final OkUrlFactory okUrlFactory; public SimpleOkHttpStack() { this(new OkHttpClient()); } public SimpleOkHttpStack(OkHttpClient okHttpClient) { if (okHttpClient == null) { throw new NullPointerException("Client must not be null."); } this.okUrlFactory = new OkUrlFactory(okHttpClient); } @Override protected HttpURLConnection createConnection(URL url) throws IOException { return okUrlFactory.open(url); }}
甚至你可以选择继承HttpClientStack ,传入OkHttp内部的OkApacheClient实现类,当然也需要添加依赖
compile 'com.squareup.okhttp:okhttp-apache:2.5.0'
public class SimpleHttpClientStack extends HttpClientStack { public SimpleHttpClientStack(OkHttpClient client) { super(new OkApacheClient(client)); }}
基本上所有的扩展方法都说了一遍,第一种是完全自己实现,第二种是选择继承现有的类去实现,这个思路和Androd开发中自定义View的思路基本一致,至于选择哪一种,完全看你自己需要。
版权声明:本文为博主原创文章,未经博主允许不得转载。
- 1楼u010028869昨天 09:01
- Android 使用OkHttp扩展Volley ,刚接触Android的volly框架,学习了