再見,volley。你好square全家桶。

好久沒有寫博客了,因爲前段時間在找工作,最近剛剛算是稍微穩定下來,在工作中我最近在學習百度地圖,ArcGIS,OpenCV這些東西的用法,對以前自學的主流Android開發的知識用到的甚少,不過我自己也沒有懈怠,在業餘時間還是在學習主流的應用開發技術和編寫代碼。

本篇文章估計是我最後一次寫有關Volley的,爲什麼這麼說。主要是因爲這個庫,隨着時間的推移,已經越來越顯現出老態。大家可以發現,Volley這個Google自家出品的庫已經更新的越來越慢,甚至是好久都沒有什麼重大改動。最明顯的一點,也是我今天重點要說的就是對OkHttp的支持問題。

Google官方早就不建議使用HttpClient來完成網絡請求,更是在Android 6.0版本中把它徹底從SDK中刪除,而另一種方式HttpURLConnection雖然在高版本中沒有明顯bug,但是它以使用複雜著稱,特別的難用。這時候square出品的okhttp則是力壓兩種原生的Http底層封裝API,被衆多開發者廣泛的接受,不僅如此,據說Android4.4的源碼中已經把默認的HttpURLConnection實現換成OkHttp實現了,可見Google官方已經承認了OkHttp的強大的存在。

從上面可以看出來,在2016年,特別是現在,都2016年年底,馬上2017了,如果你的應用還沒有用上OkHttp,那就真的有點落伍了。同樣,如果一個網絡封裝庫,它不支持OkHttp,那這個庫就真的該讓我們考慮考慮了。

什麼?你說Volley支持OkHttp?那你一定是從知乎或者微信公衆號過來的。今天我就來具體說明Volley在支持OkHttp的支持性上有多差。

首先,在OkHttp 2.x時代,Volley的確是全面支持OkHttp,你編寫一個OkHttpStack類,用它繼承Volley的HurlStack類,然後短短几十行代碼就可以寫出一個OkHttpStack類,如下所示:

package com.icon.app.util;

import com.android.volley.toolbox.HurlStack;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkUrlFactory;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * An {@link com.android.volley.toolbox.HttpStack HttpStack} implementation
 * which uses OkHttp as its transport.
 */

public class OkHttpStack extends HurlStack {

    private final OkUrlFactory okUrlFactory;

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

    public OkHttpStack(OkUrlFactory okUrlFactory) {
        if (okUrlFactory == null) {
            throw new NullPointerException("Client must not be null.");
        }
        this.okUrlFactory = okUrlFactory;
    }

    @Override
    protected HttpURLConnection createConnection(URL url) throws IOException {
        return okUrlFactory.open(url);
    }

}

基本思想就是利用了OkUrlFactory這個類,用它調用open方法並傳入url參數可以得到一個HttpURLConnection的對象。

Ok,如果你只想使用OkHttp 2的話,這就可以了,後面也不用繼續看了。

但是我想你一定知道OkHttp的最新版本是3.5.0。既然有了最新版,我們就應該使用它,特別是大版本號的更新往往代表着重大升級。但是問題來了,Volley對於okhttp3的支持變得非常牽強!

首先,第一種寫法,也就是按照上面的寫法,OkUrlFactory被註解成了過時的類,雖然很多情況下過時的API也是可以使用的,但是這個類強行使用就會報錯,導致程序崩潰。於是,你說,我們用okhttp-connection 2.x的OkUrlFactory,但OkHttpClient用OkHttp3.x的不就行了嗎,但是並不行,因爲2和3的包名不一樣,2只能和2的配套使用,3只能和3的配套使用。既然不行,那就只能換寫法。

於是,我在網上找到了第二種寫法:

package com.example.allen.volleymanager.volley;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.toolbox.HttpStack;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * Created by Allen Lin on 2016/02/17.
 */
public class OkHttp3Stack implements HttpStack {
    private final OkHttpClient mClient;

    public OkHttp3Stack(OkHttpClient client) {
        this.mClient = client;
    }

    private static HttpEntity entityFromOkHttpResponse(Response response) throws IOException {
        BasicHttpEntity entity = new BasicHttpEntity();
        ResponseBody body = response.body();

        entity.setContent(body.byteStream());
        entity.setContentLength(body.contentLength());
        entity.setContentEncoding(response.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 RequestBody createRequestBody(Request request) throws AuthFailureError {
        final byte[] body = request.getBody();
        if (body == null) return null;

        return RequestBody.create(MediaType.parse(request.getBodyContentType()), body);
    }

    private static ProtocolVersion parseProtocol(final Protocol protocol) {
        switch (protocol) {
            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");
    }

    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        int timeoutMs = request.getTimeoutMs();
        OkHttpClient client = mClient.newBuilder()
                .readTimeout(timeoutMs, TimeUnit.MILLISECONDS)
                .connectTimeout(timeoutMs, TimeUnit.MILLISECONDS)
                .writeTimeout(timeoutMs, TimeUnit.MILLISECONDS)
                .build();

        okhttp3.Request.Builder okHttpRequestBuilder = new okhttp3.Request.Builder();
        Map<String, String> headers = request.getHeaders();
        for (final String name : headers.keySet()) {
            okHttpRequestBuilder.addHeader(name, headers.get(name));
        }

        for (final String name : additionalHeaders.keySet()) {
            okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name));
        }

        setConnectionParametersForRequest(okHttpRequestBuilder, request);

        okhttp3.Request okhttp3Request = okHttpRequestBuilder.url(request.getUrl()).build();
        Response okHttpResponse = client.newCall(okhttp3Request).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;
    }
}

這個寫法直接實現HttpStack接口,看起來屌屌的,但是你應該發現了,它使用了大量apache的API,這些API在在Android 6.0版本中都被刪除了,根本用不了,於是我想了個辦法,把這個類不放在自己的包下,而是放在Volley的包下,於是,編譯期確實沒有了問題,跑起來以後發送普通的網絡請求也是沒有問題的,但是,一旦使用volley加載圖片就會報NoSuchMehodError這個迷之錯誤,我花了好長時間也發現不了到底是哪裏報出的這個錯誤,於是又只能放棄這個寫法。

再接再厲,第三種寫法。這第三種寫法,更牛逼,volley不是不支持okhttp嗎,我把volley全改了,什麼,Volley類,BaseNetwork類,OkHttpStack類等等全是自己寫的,他這個嚴格意義上來說已經不屬於對volley的擴展了,而是把它的骨架都換了,但是如果能成功運行倒是可以,可惜的是,他還是使用了apache的API,導致不能用,如果還像上面一樣,把它放在volley的包下,我覺得很不爽,因爲代碼太多,這麼多代碼放過去,volley都快不是volley了。於是第三種寫法也被拋棄。

最後一試,第四種寫法,這種寫法我也不貼了,它確實沒有用apache的API,但是,它用了過時的OkUrlFactory,並且運行起來一樣不正常,會報迷之錯誤。於是這種寫法也被拋棄。

最後的最後,我要用okhttp的雄心不死,但是隻能退而求其次,用okhttp2,我最終用了2.7.5版本。

綜上所屬,volley對最新的okhttp的支持非常不友好,如果去看volley的源碼,它還對HttpClient保留了支持,並且項目內部大量使用了apache的API,這讓人感覺非常不爽。如果我是volley的作者,我早就讓HttpClient狗帶了。而且之前也說了,從更新頻率看,volley的維護已經變的相當不積極,作爲一個Google官方出品的庫,我不知道現在Google對它的態度如何,但我覺得大致上來看,是相當不樂觀的,特別是square全家桶橫行天下的現在,在網絡請求庫中,底層支持且僅支持okhttp的Retrofit獨領風騷,我覺得Google沒有興趣非要弄一個可以碾壓Retrofit的庫才肯罷休,何不花時間做點別人沒有做過的事情。

我以前喜歡volley的原因還有一個就是因爲它可以簡單的加載圖片,但是功能實在是太有限了,我以前也寫過幾篇文章,專門寫當volley加載圖片遇到功能瓶頸的時候怎麼解決。在功能上volley被圖片加載四大庫完全吊打。 

綜上所屬,volley最終和一些老庫一樣走向停更是必然的,我不準備再花時間和精力去解決volley的瓶頸,我要向square低頭了,我要向Jack Wharton大神低頭了,我準備轉向Retrofit了,所以,小夥伴們,如果你還在用volley,確實該考慮考慮了。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章