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

感謝這個作者分享精神,幫我解決了這種問題。哎,還是自己思考的角度不夠,沒有從雲端的角度去分析。所以在這裏記錄下,以後多換角度去思考問題。

發佈了56 篇原創文章 · 獲贊 50 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章