Volley配置OkHttp的那些事儿

前言

打一来这公司,就使用的是volley框架进行网络请求。众所周知,目前最火的就是retrofit+okhttp+rxjava,只因一直在开发新功能,没有排开时间来替换,所以就将就着用了。可问题来了,最近老大总是抱怨android的网络请求慢,而且总是会超时,体验了一下公司ios客户端,网络请求确实快,也不会出现超时这种现象。怎么办呢?看下SD卡的网络错误日志,

com.android.volley.VolleyError.TimeoutError

看到这里,马上我们就会觉得偶尔出现这个是正常的,网络问题嘛,随时可能抽风。最多就是把网络连接超时时间设置长点。我们知道,volley默认的网络超时时间是2.5秒。所以,这对于弱网络环境下是不够的。于是,我把时间设置为了10s,如下

 //操作超时时间
 public static final int CUD_SOCKET_TIMEOUT = 10000;
//最大重试请求次数
public static final int MAX_RETRIES = 0;
request.setRetryPolicy(new DefaultRetryPolicy(CUD_SOCKET_TIMEOUT,
                MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

这里通过setRetryPolicy方法来设置,传入RetryPolicy参数,这里我们创建它的继承类DefaultRetryPolicy就好,超时时间设置为10s,重试次数为0,最好设置为0。如果设置为>0的次数,volley好像有个BUG,在弱网络情况下会进行两次请求。
改好之后,这就完事了吗?NO,还是会间隔性的出现TimeOut,怎么办,再加长时间?我们也要考虑下用户体验,在10s内请求到还算好,超过10s用户体验就不好了。既然还是不行,那就用okhttp请求吧,要知道,volley使用的是HttpUrlConnect和HttpClient进行请求的。请求速度在网络框架里面并不是最好的,它适用轻量级数据高并发请求。那么为什么要换 okhttp呢,相对来说,okhttp请求速度还是比HttpUrlConnect更快的,因为oKHttp使用的请求类似于增强版的HTTPURLConnect。它的好处有如下几个:
1、支持HTTP1.1/HTTP1.2/SPDY协议,要知道谷歌浏览器现在也用SPDY协议了。
2,默认支持HTTPS请求,CA颁发的正式,自签名不行
3,HTTP请求的header体和body体GZip压缩
4,使用阻塞式线程池,优化网络请求队列
5、支持拦截器,调试方便

所以,干嘛不换呢。那么,我们又该怎么配置Volley,使Volley使用OKHTTP请求呢,通过查看代码,我们发现,我们可以传递自己的HttpStack,那么就好办了,我们创建一个HttpStack的子类就好了。

接下来在build.gradle文件里面导入okhttp3。

 compile 'com.squareup.okhttp3:okhttp:3.5.0'
 compile('com.squareup.okhttp3:logging-interceptor:3.5.0')
 compile 'com.squareup.okio:okio:1.7.0'

然后加上volley的jar包,接下来写HTTPStack的子类OkHttpStack

public class OkHttpStack implements HttpStack
{
    private OkHttpClient okHttpClient;
    public OkHttpStack()
    {
        okHttpClient = new OkHttpClient();
    }
    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError
    {
        int timeOutMs = request.getTimeoutMs();
      /*  HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                ZLogUtil.d("zgx", "OkHttp====headAndBody" + message);
            }
        });
        //包含header、body数据
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);*/
        Dispatcher dispatcher = okHttpClient.dispatcher();
        dispatcher.setMaxRequestsPerHost(15);
        OkHttpClient.Builder okHttpBuilder = new OkHttpClient().newBuilder();
        OkHttpClient resultOkHttpClient = okHttpBuilder
                .connectTimeout(timeOutMs, TimeUnit.MILLISECONDS)
                .readTimeout(timeOutMs,TimeUnit.MILLISECONDS)
                .writeTimeout(timeOutMs,TimeUnit.MILLISECONDS)
                .dispatcher(dispatcher)
                //.addInterceptor(loggingInterceptor)
                .build();
        Map<String, String> headerMaps = request.getHeaders();
        Headers headers = Headers.of(headerMaps);
        okhttp3.Request.Builder okHttpReqBuilder = new okhttp3.Request
                .Builder()
                .url(request.getUrl())
                .headers(headers);
        setConnectionParametersForRequest(okHttpReqBuilder, request);
        Response okHttpResponse = resultOkHttpClient
                .newCall(okHttpReqBuilder.build())
                .execute();
        StatusLine responseStatus = new BasicStatusLine
                (
                        parseProtocol(okHttpResponse.protocol()),
                        okHttpResponse.code(),
                        okHttpResponse.message()
                );
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        response.setEntity(entityFromOkHttpResponse(okHttpResponse));
        Headers responseHeaders = okHttpResponse.headers();
        for (int i = 0, len = responseHeaders.size(); i < len; i++)
        {
            final String name = responseHeaders.name(i), value = responseHeaders.value(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;
    }

    @SuppressWarnings("deprecation")
    private static void setConnectionParametersForRequest
            (okhttp3.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单例里面配置下就好了。

 public void initialize(Context context) {
        if(mRequestQueue==null)
        mRequestQueue = Volley.newRequestQueue(context,new OkHttpStack());
        mRequestQueue.start();
    }

这样,就配置好了Volley使用OkHttp请求了。接下来奇葩的事就出来了。

1,在公司wifi,android访问云端还是会出现间隔性的TimeoutError问题,而且是毫无规律的,ios访问就不会有这个现象
2、在家用wifi,访问就确实是比以前快多了。
3、使用手机数据连接3G或者4G访问也快。

这里记录下自己尝试的方法

1、优化网络请求频率,要知道,没有配置okhttp的情况下,volley在弱网的情况下,网络延时还是挺大的,优化的并不够好。
2,使用fidder手机抓包,抓包的时候不会出现这个问题。
3,ping云端ip,看是否会出现timeOutError问题,可以使用oneAPM或者jmeter压力测试。
4,猜想公司使用的是二级域名,后来发现公司使用wifi和家用wifi没有什么区别的,都是AP+路由。
5、是否和HTTPS和HTTPDNS有关,后来发现也没有关系。
6、通过Dispatcher加大okhttp每秒的最大请求数。

经过多种方式尝试,结果并没有解决公司wifi情况下这种间隔性超时的问题。最后,经过各种google搜索,终于找到一篇博客,和这种情况一模一样的,阿里云ECS上APP访问异常或超时

原因:
导致请求阿里云ECS上的APP访问超时或访问慢,是由于移动端发送过来的TCP包的时间戳是乱的,所以服务器将这些包给丢弃了。

解决方案:
#vim /etc/sysctl.config
net.ipv4.tcp_tw_recycle = 0  // net.ipv4.tcp_tw_recycle 设置为1,则开启!设置为0为关闭!记录时间戳
#sysctl  -p

感谢这个作者分享精神,帮我解决了这种问题。哎,还是自己思考的角度不够,没有从云端的角度去分析。所以在这里记录下,以后多换角度去思考问题。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值