当前位置: 代码迷 >> Android >> Android 使用OkHttp扩张Volley
  详细解决方案

Android 使用OkHttp扩张Volley

热度:69   发布时间:2016-04-27 22:32:16.0
Android 使用OkHttp扩展Volley

我们先来回忆一下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框架,学习了
  相关解决方案