好久沒有寫博客了,因爲前段時間在找工作,最近剛剛算是稍微穩定下來,在工作中我最近在學習百度地圖,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,確實該考慮考慮了。