OkHttp 中的参数封装

5,939 阅读7分钟

废话部分

只是在工作上的一些无聊的事情,想闲扯的可以看下,不想的可以用直接看下一段的正题。
我是两个月前换的一个工作,去的一个公司只有我一个人开发,并且是从零开始,什么都没有的情况下我还是去了,因为我个人觉得这个机会不错,以前工作了两年都是copy别人的代码或者维护别人的代码。项目也是不伦不类的MVC,网络请求、缓存、项目架构现在看来都是糟的一塌糊涂(现在这么认为,因为这两个月自己感觉自己有了很多提高),不过当时的我竟然那么安逸的选择在那里一直待下去,很庆幸自己已经出来了,不然面对现在这么激烈的竞争,我想我可能就被市场淘汰了。可能现在还有很多人在一个比较安逸的公司拿着不错的薪资一直没有跳槽的想法,我只是给大家一个建议,如果你喜欢技术,喜欢挑战,建议早些脱离这些公司,把每个公司当成自己技术增长的跳板,不过不建议不到一年就换工作的,太频繁了也不好,最起码要把这个公司用到的技术全部学到了才能走,不然到最后还是很难学到东西。好了不废话了。开始今天的正题了。

Retrofit??

现在Retrofit可能很多人都有了解,毕竟现在很多开发者社区到处可见的标题都是Retrofit+RxJava+MVP这些文章,刚开始自己看这些的时候也有些懵,不过多研究一下,在项目中用一下就发现其实这些东西都不是很难,给开发者一些建议,别上来就去看一些开源项目的源码,那样你会一头懵逼的,大家可以尝试去了解应用场景,尝试在项目中或者自己写个东西去实践一下,然后再去看源码。
Retrofit通俗的说就是一个HTTP请求的一个框架,通过一个Interface来定义api的实现,通过注释的方式进行一些请求的设置。

public interface GitHubService 
{
    @GET("users/{user}/repos") 
    Call> listRepos(@Path("user") String user);
}

就像上面的代码一样,所有的网络请求接口都是定义在一个Interface中,然后在使用的时候通过Retrofit中的create()方法即可实现,详细的可以看
[Retrofit官网](square.github.io/retrofit/
[Retrofit api文档](square.github.io/retrofit/2.…
有关Retrofit的在这里不多说,因为本篇主要是来说在使用过程中有关OkHttp的事情,所以想了解Retrofit的可以去查询其他的资料

Retrofit中OkHttp使用

大家都知道Retrofit中默认使用的网络请求方式是使用OkHttp的,先了解下OkHttp
OkHttp的官网介绍和Retrofit很像,也是一个基于Http的一个请求客户端,毕竟Retrofit和OkHttp都是square的开源项目,下面来说下我在项目里面使用的时候有关okhttp的配置问题。
首先okhttp的确非常棒,在接入到项目里面使用的时候日志输入非常详细,类似Content-type,header、参数返回值这些全部都很详细的显示,Okhttp最棒的一个设计就是整个网络请求过程中我们可以用在任意一个地方去做一个拦截处理,我们可以通过实现提供的接口[Interceptor](square.github.io/okhttp/3.x/…
下面我来举个例子:
我在和我们后台人员沟通的时候发现我们每个接口都有两个共同的参数 version和device,我们不可能在定义的时候每个接口里面都去添加上这两个参数,为了方便我们会在一个地方统一的去添加,那么我们就可以通过自定义一个Interceptor来实现,具体代码如下:

public class CommonParamsInterceptor implements Interceptor
{
    @Override
    public Response intercept(Chain chain) throws IOException
    {
        Request request = chain.request();
        //get请求后面追加共同的参数
        HttpUrl httpUrl = request.url().newBuilder()
                                 .addQueryParameter("version", ConstantConfig.APP_VERSION_CODE)
                                 .addQueryParameter("token", ConstantConfig.APP_TOKEN)
                                 .addQueryParameter("device", "Android").build();
        request = request.newBuilder().url(httpUrl).build();
        return chain.proceed(request);
    }
}

这样就可以实现每个请求上面添加上参数,不过有些事情总是比你想象的蛋疼一些,因为后台接口是两个人开发的,一个人告诉我所有的请求方法是get和post都支持,当时这样写也没发现什么问题,后台和另一个人调试的时候发现不行,找了半天原因才发现原来上面的写法是吧这些参数都放到了url的后面,也就是get请求的传参方式,所以如果限定了必须使用post请求那么这个方法就不行了。唉。。。没办法,只能去修改,然后就是一番查找,终于解决了,代码如下:

public class CommonParamsInterceptor implements Interceptor
{
    @Override
    public Response intercept(Chain chain) throws IOException
    {
        Request request = chain.request();
        //post请求追加参数
        FormBody.Builder newBody = new FormBody.Builder();
        newBody.add("version", ConstantConfig.APP_VERSION_CODE)
               .add("token", ConstantConfig.APP_TOKEN).add("device", "Android").build();
        //拦截请求中用户传入的数据,把参数遍历放入新的body里面,然后进行一块提交
        FormBody oldBody = (FormBody) request.body();
        for (int i = 0; i < oldBody.size(); i++)
        {
            newBody.add(oldBody.encodedName(i), oldBody.encodedValue(i));
        }
        request = request.newBuilder().post(newBody.build()).build();
        chain.proceed(request);
    }
}

这样就实现了表单数据的提交,get和post的设置共同参数的方式。从这一个例子就可以看出使用okhttp的时候我们可以在每一个步骤进行拦截处理,比如设置缓存、头部信息等等。
自定义的Interceptor可以在初始化okhttp的时候设置,如:

 /**
     * OkHttp的初始化,设置缓存目录,设置请求最大时间,请求失败重连
     */
    public void initOkHttp()
    {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        CustomParamsInterceptor paramsInterceptor = new CustomParamsInterceptor();
        builder.addInterceptor(paramsInterceptor);
        if (BuildConfig.DEBUG)
        {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(loggingInterceptor);
        }
        // 缓存 http://www.jianshu.com/p/93153b34310e
        File cacheFile = new File(ConstantConfig.CACHE_DIR, "/NetCache");
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
        Interceptor cacheInterceptor = new Interceptor()
        {
            @Override
            public Response intercept(Chain chain) throws IOException
            {
                Request request = chain.request();
                if (!Util.isNetworkConnected(App.getmAppContext()))
                {
                    request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
                }
                Response response = chain.proceed(request);
                if (Util.isNetworkConnected(App.getmAppContext()))
                {
                    int maxAge = 0;
                    // 有网络时 设置缓存超时时间0个小时
                    response.newBuilder().header("Cache-Control", "public, max-age=" + maxAge)
                            .build();
                } else
                {
                    // 无网络时,设置超时为4周
                    int maxStale = 60 * 60 * 24 * 28;
                    response.newBuilder().header("Cache-Control",
                                                 "public, only-if-cached, max-stale=" + maxStale)
                            .build();
                }
                return response;
            }
        };
        builder.cache(cache).addInterceptor(cacheInterceptor);
        //设置超时
        builder.connectTimeout(15, TimeUnit.SECONDS);
        builder.readTimeout(20, TimeUnit.SECONDS);
        builder.writeTimeout(20, TimeUnit.SECONDS);
        //错误重连
        builder.retryOnConnectionFailure(true);
        okHttpClient = builder.build();
    }

okhttp很方便,很灵活,我们可以随意的折腾。不过,话说,最气人的是因为ios那边的问题,接口竟然要求我参数的传递用json格式。

这里为大家说下数据传输格式和数据格式是两个不同的东西,我们一般认为数据格式使我们手机在接收接口api返回的数据是一个json的String串,但是和后台接口传递数据的格式有以下几种:

  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

    我们在不设置默认的情况下http请求的数据传输格式是最后一个application/x-www-form-urlencoded ,这个设置是一key value的形式传递的参数,但是我们的后台要求我使用application/json的格式去传递数据,唉。。。。那以上两种方法就不可行了,没办法,接着一番,最终也是搞出来了,代码如下:

public class CustomParamsInterceptor implements Interceptor
{
    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

    @Override
    public Response intercept(Chain chain) throws IOException
    {
        Request request = chain.request();
        ArrayMap paramsMap = new ArrayMap();
        paramsMap.put("version", "1.0");
        paramsMap.put("token", "");
        paramsMap.put("device", "Android");
        if (request.body() instanceof FormBody)
        {
            FormBody oldBody = (FormBody) request.body();
            for (int i = 0; i < oldBody.size(); i++)
            {
                paramsMap.put(oldBody.encodedName(i), oldBody.encodedValue(i));
            }
        }
        Gson gson = new Gson();
        AppLog.i("Gson参数格式---" + gson.toJson(paramsMap));

        RequestBody body = RequestBody.create(JSON, gson.toJson(paramsMap));
        request = request.newBuilder().post(body).build();
        return chain.proceed(request);
    }

}

这就是我今天所有的经历了,我每天都会写一篇关于在开发中遇到的问题和学到的技术点,如果有兴趣的可以关注,虽说现在还是个小白,不过努力总不会白费的,加油吧骚年们。
我的项目使用了MVP+RxJava+Retrofit 很多东西都还在持续的完善中,如果有兴趣的大家可以一起讨论。感谢大家的阅读。