美文网首页
使用okHttp、Volley、Gson快速组装HttpClin

使用okHttp、Volley、Gson快速组装HttpClin

作者: 橡樹先生 | 来源:发表于2017-11-01 14:45 被阅读86次

文章写了很久了,之前发布在我自己维护的网站上,文章中提到的支持库请自己视情况更新到最新版本。

什么?你还不知道这3个货什么东西?
好吧,请移步这里 OkHttp使用介绍Android库Volley的使用介绍GSON使用的学习笔记,入门篇

Foreword

  • 这并不是一篇特别有营养的技术博客,对于相关库的使用并没有做深入的解释,因为网上有很多翔实的解析和介绍,我会给出连接。

  • 服务端返回的是是json格式的数据。使用Android Studio 1.2.2 & Gradle。

为什么是OKHttp,Volley,Gson ?

原因有二:
一、水平不够,不能自己写出牛逼高效简单易用万人称赞而且代码好看的网络通信模块。
二、因为他们的优点。

OKhttp: 如果你看了上面第一篇的Blog,你就会发现OKHttp使用起来方便而且我们不用去考虑HttpURLConnectionHttpClient的那点破事。

Volley: 对于Volley深层次的解析和源码的讲解可以看这里 Volley 源码解析如果你对Volley了解不多,请务必看下这篇文章),Volley各种牛逼介绍我就不再重复。

我们看重的是他的优点:“扩展性强,Volley 中大多是基于接口的设计,可配置性强。”。
作为一个强大的CV战士,既然是要动手组装,那么优秀的扩展性,就是必须的了。

Gson: 其实Gson并不是目前来说最好用的Json解析的工具,看图, Gson的解析能力并不是最优秀的,而且据说还有些小坑。但是你要知道Gson的lib只有几百k,另外Android Studio中竟然有GsonFormat的插件,分分中快速生成Model。就是要做快。枪。。手。。。

怎么组装?

对于Volley,处理Http请求使用的是HttpURLConnectionHttpClient

Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现 。

显而易见,我们要用OKHttp的底层通信处理来代替Volley的方案》

Volley提供StringRequestJsonRequest,以及ImageRequest(这里关于Volley Image相关的不作涉及,有需求的请自行改造)。并不能完全满足我们的需求。

所以我们使用Gson来自定义自己的CustomRequest

1. 添加相关的支持库

compile files('libs/gson-2.3.1.jar')
compile 'com.mcxiaoke.volley:library:1.0.17'
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0'
compile 'com.squareup.okio:okio:1.5.0'

2. 配置Volley

根据官方的Training教程 最基本的我们需要这么写 (很多教程都推荐写到Application中,也是ok的):

public class HttpClientRequest {

    private static HttpClientRequest mInstance;
    private static Context mCtx;
    public RequestQueue mRequestQueue;

    private HttpClientRequest(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();
    }

    public static synchronized HttpClientRequest getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new HttpClientRequest(context);
        }
        return mInstance;
    }

    /**
     * Returns a Volley request queue for creating network requests
     *
     * @return {@link com.android.volley.RequestQueue}
     */
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    /**
     * Adds a request to the Volley request queue
     *
     * @param request is the request to add to the Volley queue
     */
    public <T> void addRequest(Request<T> request) {
        getRequestQueue().add(request);
    }
}

3. 使用OKHttp的底层通信处理来代替Volley的方案

这个方案的基础是 Volley支持自定义HttpStack

Allow custom HttpStack in Volley.newRequestQueue.

Add a variant method that allows the user to pass in
an HttpStack to be passed to BasicNetwork. Makes using
alternative stacks like OkHttp easier.

首先这个问题一点都不新鲜,因为很早就有人想这么干了,有人在Stack Overflow提问了这个问题How to implement Android Volley with OkHttp 2.0?,包括 Jake Wharton 说过可以这么搞

那么现在问题的关键就是怎么搞的问题, 很早 jake大神提出一个方案 OkHttpStack.java ,但是随着OKhttp的更新,最初的方法已经不能使用了,逐渐的就有人在使用过程中又了更完善的方案:
OkHttpStack.java。看起来不很不错的样子,好就用这个了。
由于这个写法在api25之后有很多类过时了,因此我做了一些修改:

/**
 * An {@link HurlStack HurlStack} implementation which
 * uses OkHttp as its transport.
 */
public class OkHttpStack extends HurlStack {
    private final OkUrlFactory mFactory;

    public OkHttpStack() {
        this(new OkHttpClient());
    }

    public OkHttpStack(OkHttpClient client) {
        if (client == null) {
            throw new NullPointerException("Client must not be null.");
        }
        // Interceptors not work for OkUrlFactory
//        client.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);
        mFactory = new OkUrlFactory(client);
    }

    @Override
    protected HttpURLConnection createConnection(URL url) throws IOException {
        return mFactory.open(url);
    }
    /** Dangerous interceptor that rewrites the server's cache-control header. */
    private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
        @Override public Response intercept(Chain chain) throws IOException {
            Response originalResponse = chain.proceed(chain.request());
            return originalResponse.newBuilder()
                    .header("Cache-Control", "max-age=60")
                    .build();
        }
    };
}

ok,现在把我们的HttpClientRequest中getRequestQueue()方法修改下。

....
 /**
     * Returns a Volley request queue for creating network requests
     *
     * @return {@link com.android.volley.RequestQueue}
     */
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            // use  custom okhttpStack, make better work .
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(),
                    new OkHttpStack(new OkHttpClient()));
        }
        return mRequestQueue;
    }
...

4. 自定义Request

对于这个,官方的 Training教程 是这样的写的:

public class GsonRequest<T> extends Request<T> {
    private final Gson gson = new Gson();
    private final Class<T> clazz;
    private final Map<String, String> headers;
    private final Listener<T> listener;

    /**
     * Make a GET request and return a parsed object from JSON.
     *
     * @param url URL of the request to make
     * @param clazz Relevant class object, for Gson's reflection
     * @param headers Map of request headers
     */
    public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
            Listener<T> listener, ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        this.clazz = clazz;
        this.headers = headers;
        this.listener = listener;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers != null ? headers : super.getHeaders();
    }

    @Override
    protected void deliverResponse(T response) {
        listener.onResponse(response);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            return Response.success(
                    gson.fromJson(json, clazz),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }
}

这个Request对付基本的也就够用了,但是在使用过程中,发现一些问题,Volley竟然没有提供设置POST参数的方法,但是当发出POST请求的时候,Volley会尝试调用Request中的getParams()方法来获取POST参数。ok,改一下,添加下getParams()方法,再来个构造方法

    
        /**
     * Make a request and return a parsed object from JSON.
     *
     * @param url     URL of the request to make
     * @param clazz   Relevant class object, for Gson's reflection
     * @param headers Map of request headers
     */
    public CustomRequest(int method, String url, Class<T> clazz, Map<String, String> headers,
                         Map<String, String> params,
                         Response.Listener<T> listener, Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        this.clazz = clazz;
        this.headers = headers;
        this.params = params;
        this.listener = listener;
    }
    

    @Override
    protected Map<String, String> getParams() throws AuthFailureError {
        return params != null ? params : super.getParams();
    }

但是这样用起来还是不太爽,因为我要用时候需要在activity里new这个Request,这样看起来代码并不好看。我想放到HttpClientRequest中,封装起来。但是如果我想同时设置 methodurlclassheadersparams 还有listner等等。代码还是不怎么不好看不说,好像用起来也不太方便,扩展也不太好。我参考了下okhttp是这样写的

Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();

好像很不错的样子,我们也根据自己的需求这样搞一下,

最后我们成形的CustomRequest就是这样了:

**
 * MyApplication
 * Created by acer_april
 * on 2015/7/20
 * Description: customVolleyRequest
 */
public class CustomRequest<T> extends Request<T> {
    private final Gson gson = new Gson();
    private final Class<T> clazz;
    private final Map<String, String> headers;
    private final Response.Listener<T> listener;
    private Map<String, String> params;


    /**
     * Make a GET request and return a parsed object from JSON.
     *
     * @param url    URL of the request to make
     * @param clazz  Relevant class object, for Gson's reflection
     * @param params Map of request params
     */
    public CustomRequest(String url, Class<T> clazz, Map<String, String> params,
                         Response.Listener<T> listener, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        this.clazz = clazz;
        this.headers = null;
        this.params = params;
        this.listener = listener;
    }

    /**
     * Make a request and return a parsed object from JSON.
     *
     * @param url     URL of the request to make
     * @param clazz   Relevant class object, for Gson's reflection
     * @param headers Map of request headers
     */
    public CustomRequest(int method, String url, Class<T> clazz, Map<String, String> headers,
                         Map<String, String> params,
                         Response.Listener<T> listener, Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        this.clazz = clazz;
        this.headers = headers;
        this.params = params;
        this.listener = listener;
    }

    /**
     * @param builder requestBuilder
     */
    public CustomRequest(RequestBuilder builder) {
        super(builder.method, builder.url, builder.errorListener);
        clazz = builder.clazz;
        headers = builder.headers;
        listener = builder.successListener;
        params = builder.params;
    }


    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers != null ? headers : super.getHeaders();
    }

    @Override
    protected Map<String, String> getParams() throws AuthFailureError {
        return params != null ? params : super.getParams();
    }

    @Override
    protected void deliverResponse(T response) {
        listener.onResponse(response);
    }


    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            if (clazz == null) {
                return (Response<T>) Response.success(parsed,
                        HttpHeaderParser.parseCacheHeaders(response));
            } else {
                return Response.success(gson.fromJson(parsed, clazz),
                        HttpHeaderParser.parseCacheHeaders(response));
            }
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        } catch (Exception e) {
            return Response.error(new ParseError(e));
        }

    }

    /**
     * requestBiulder  使用方法参见httpClientRequest
     */
    public static class RequestBuilder {
        private int method = Method.GET;
        private String url;
        private Class clazz;
        private Response.Listener successListener;
        private Response.ErrorListener errorListener;
        private Map<String, String> headers;
        private Map<String, String> params;

        public RequestBuilder url(String url) {
            this.url = url;
            return this;
        }

        public RequestBuilder clazz(Class clazz) {
            this.clazz = clazz;
            return this;
        }

        public RequestBuilder successListener(Response.Listener successListener) {
            this.successListener = successListener;
            return this;
        }

        public RequestBuilder errorListener(Response.ErrorListener errorListener) {
            this.errorListener = errorListener;
            return this;
        }

        public RequestBuilder post() {
            this.method = Method.POST;
            return this;
        }

        public RequestBuilder method(int method) {
            this.method = method;
            return this;
        }

        public RequestBuilder addHeader(String key, String value) {
            if (headers == null)
                headers = new HashMap<>();
            headers.put(key, value);
            return this;
        }

        public RequestBuilder headers(Map<String, String> headers) {
            this.headers = headers;
            return this;
        }

        public RequestBuilder params(Map<String, String> params) {
            post();
            this.params = params;
            return this;
        }

        public RequestBuilder addParams(String key, String value) {
            if (params == null) {
                params = new HashMap<>();
                post();
            }
            params.put(key, value);
            return this;
        }

        public RequestBuilder addMethodParams(String method) {
            if (params == null) {
                params = new HashMap<>();
                post();
            }
            params.put("method", method);
            return this;
        }

        public CustomRequest build() {
            return new CustomRequest(this);
        }
    }


封装方法的时候这样写,另外加上取消请求的方法。


     /**
     * Returns a Volley request queue for creating network requests
     *
     * @return {@link com.android.volley.RequestQueue}
     */
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            // use  custom okhttpStack, make better work .
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(),
                    new OkHttpStack(new OkHttpClient()));
        }
        return mRequestQueue;
    }
 /**
     * Cancels all the request in the Volley queue for a given tag
     *
     * @param tag associated with the Volley requests to be cancelled
     */
    public void cancelAllRequests(String tag) {
        if (getRequestQueue() != null) {
            getRequestQueue().cancelAll(tag);
        }
    }
    }

    /**
     * Adds a request to the Volley request queue
     *
     * @param request is the request to add to the Volley queuest
     * @param tag is the tag identifying the request
     */
    public <T> void addRequest(Request<T> request, String tag) {
        request.setTag(tag);
        getRequestQueue().add(request);
    }

   /**
     * 使用和参数配置范例
     *
     * @param param1
     * @param param2
     * @param listener
     * @param errorListener
     */
    public void getDemoData(String param1,
                            String param2,
                            Response.Listener listener,
                            Response.ErrorListener errorListener,String tag) {
        Map<String, String> params = new HashMap<>();
        params.put("param1", param1);
        params.put("param2", param2);

        CustomRequest request = new CustomRequest.RequestBuilder()
//                .post()//不设置的话默认GET 但是设置了参数就不需要了。。。
                .url("")//url会统一配置到requestUrl类中 
                .addMethodParams("") //请求的方法名
                        // 添加参数方法1 适用参数比较多的情况下
//                .params(params)
                        // 添加参数方法2
                .addParams("param1", param1)//添加参数1
                .addParams("param2", param2)//添加参数2
//                .clazz(Test.calss) //如果设置了返回类型,会自动解析返回model 如果不设置会直接返回json数据;
                .successListener(listener)//获取数据成功的listener
                .errorListener(errorListener)//获取数据异常的listener
                .build();
        addRequest(request,tag);
        //将请求add到队列中。并设置tag  并需要相应activity onStop方法中调用cancel方法
    }

ok,大功告成,组装完毕。


最后,源码下载
我在持续更新:

2016-02-24 更新:添加 转换params为json格式的post请求,使用方法见demo。
2016-03-29 更新:添加 直接使用json格式的数据请求。
2016-03-29 更新:添加 使用OkHttp封装文件上传的Request,并添加progress 回调(回调非UI线程,请在使用自行用Handler处理),具体使用请见 UploadFileRequest.java。
2016-04-29 更新:优化 文件上传方法,修复 存在的可能导致内存泄露的问题。
2016-05-18 更新:修复由于添加json格式请求导致的正常post请求异常的问题。
2017-01-06 更新:添加 使用OkHttp封装的文件下载的Request,并添加progress 回调(回调非UI线程,请在使用自行用Handler处理),具体使用请见 UploadFileRequest.java。
2017-01-06 更新:添加 将配置好参数的post请求转换为get请求,使用方法以及注意事项见demo。
2017-01-06 更新:添加 在网络没有连接的情况下 NoConnectionError,返回最近一次的请求缓存。用于没有网络的情况下,加载上一次的数据。注意,需要配合使用get请求,因为volley缓存的key是请求的url。与上一条配合使用效果最好。

参考资料

声明

  1. 由于互联网数据的分享性,如果我发表的文章,来源于您的原创文章,且我没有注明,请微博私信或者邮件macouen@gmail.com说明。
  2. 欢迎转载,但请注明文章原始出处。

作者:Oak_Zmm
出处:http://oakzmm.com/

相关文章

网友评论

      本文标题:使用okHttp、Volley、Gson快速组装HttpClin

      本文链接:https://www.haomeiwen.com/subject/ezfgpxtx.html