volley暂时是不支持HTTP/2的,这是官方的解释Does volley Support HTTP/2
不过volley设计的扩展性特别好,volley提供的接口HttpStack
,可以自己去实现来支持HTTP/2。在Android上,从4.4以后系统就已经支持HTTP/2,但是不稳定,有bug,直到Android5.0才修复bug。第三方库中,有OkHttp
是已经支持了HTTP/2,所以我们可以使用OkHttp
来实现。
具体实现,可以参考OkHttp3Stack
自己的实现如下:
@SuppressWarnings("deprecation")
public class OkHttpStack implements HttpStack {
private final OkHttpClient mOkHttpClint;
public OkHttpStack(OkHttpClient okHttpClient) {
this.mOkHttpClint = okHttpClient;
}
public OkHttpStack() {
mOkHttpClint = new OkHttpClient();
}
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
OkHttpClient.Builder builder = mOkHttpClint.newBuilder();
int timeoutMs = request.getTimeoutMs();
builder.connectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
builder.readTimeout(timeoutMs, TimeUnit.MILLISECONDS);
builder.writeTimeout(timeoutMs, TimeUnit.MILLISECONDS);
okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
requestBuilder.url(request.getUrl());
Map<String, String> headers = request.getHeaders();
if (headers != null) {
for (String key : headers.keySet()) {
String value = headers.get(key);
if (TextUtils.isEmpty(value)) {
continue;
}
requestBuilder.addHeader(key, value);
}
}
if (additionalHeaders != null) {
for (String key : additionalHeaders.keySet()) {
String value = additionalHeaders.get(key);
if (TextUtils.isEmpty(value)) {
continue;
}
requestBuilder.addHeader(key, value);
}
}
setConnectionParametersForRequest(requestBuilder, request);
okhttp3.Request okRequest = requestBuilder.build();
OkHttpClient okClient = builder.build();
Call call = okClient.newCall(okRequest);
Response okResponse = call.execute();
BasicStatusLine statusLine = new BasicStatusLine(parseProtocol(okResponse.protocol()), okResponse.code(), okResponse.message());
BasicHttpResponse httpResponse = new BasicHttpResponse(statusLine);
httpResponse.setEntity(entityFromOkHttpResponse(okResponse));
Headers okHeaders = okResponse.headers();
for (int i = 0; i < okHeaders.size(); i++) {
String name = okHeaders.name(i);
String value = okHeaders.value(i);
if (name != null) {
httpResponse.addHeader(new BasicHeader(name, value));
}
}
return httpResponse;
}
@SuppressWarnings("deprecation")
/* package */ private static void setConnectionParametersForRequest( /*HttpURLConnection connection*/
okhttp3.Request.Builder builder, Request<?> request) throws IOException, AuthFailureError {
CUtils.logD("OK","---request url : " + request.getUrl());
switch (request.getMethod()) {
case Request.Method.DEPRECATED_GET_OR_POST:
byte[] postBody = request.getPostBody();
if (postBody != null) {
// connection.setRequestMethod("POST");
// addBody(connection, request, postBody);
builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()), postBody));
}
break;
case Request.Method.GET:
// Not necessary to set the request method because connection defaults to GET but
// being explicit here.
// connection.setRequestMethod("GET");
builder.get();
break;
case Request.Method.DELETE:
// connection.setRequestMethod("DELETE");
builder.delete();
break;
case Request.Method.POST:
// connection.setRequestMethod("POST");
// addBodyIfExists(connection, request);
builder.post(addBodyIfExists(request));
break;
case Request.Method.PUT:
// connection.setRequestMethod("PUT");
// addBodyIfExists(connection, request);
builder.put(addBodyIfExists(request));
break;
case Request.Method.HEAD:
// connection.setRequestMethod("HEAD");
builder.head();
break;
case Request.Method.OPTIONS:
// connection.setRequestMethod("OPTIONS");
builder.method("OPTIONS", null);
break;
case Request.Method.TRACE:
// connection.setRequestMethod("TRACE");
builder.method("TRACE", null);
break;
case Request.Method.PATCH:
// connection.setRequestMethod("PATCH");
// addBodyIfExists(connection, request);
builder.patch(addBodyIfExists(request));
break;
default:
throw new IllegalStateException("Unknown method type.");
}
}
private static @Nullable
RequestBody addBodyIfExists(Request<?> request) throws AuthFailureError {
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");
}
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;
}
}
注意
如果项目使用了Tinker,请注意下面所写
在Android 9.0上面Apache HTTP 客户端弃用,bootclasspath 中已移除(org.apache.http.legacy.boot.jar)
请使用gradle配置,使用` useLibrary 'org.apache.http.legacy'`,而不要再libs中放置`org.apache.http.legacy.jar ` jar包
// Apache HTTP 客户端弃用,bootclasspath 中已移除
//AndroidP开始org.apache.http.legacy.jar这个包不在打包进bootclasspath,对于targetsdkversion>=28的,继续引用此包的应用将直接出现NoClassDefFoundError,
// 对于targetsdkversion低于28的,系统会将此包加载到应用的classloader中,如果应用采用系统的classloader加载应用,将同样会出现NoClassDefFoundError,建议模块使用 HttpURLConnection 类,更加高效。
//移除libs中的org.apache.http.legacy.jar,保证自己apk中没有这个jar,否则在android9.0上热更新之后会导致此jar中的org.apache.http.ProtocolVersion会参与运行,就会抛异常,参考源码
/**
* public ProtocolVersion(String protocol, int major, int minor) {* throw new RuntimeException("Stub!");
*}* */
//这个jar其实只是用于编译,不能参与运行
//引用github的回答
//依赖的三方库中有HttpClient ,引入的时候有人在项目中加入了org.apache.http.legacy.jar 。
//
//解决方案:删除 org.apache.http.legacy.jar 。 用 useLibrary 'org.apache.http.legacy' 或者 想办法compileOnly org.apache.http.legacy.jar ,或者找一份 httpclient源码导入项目中、会增加包体积,并不会所谓『冲突』。
//保证apk内没有legacy.jar class文件
//
//问题原因 9.0 之前 /system/framework/org.apache.http.legacy.boot.jar 一直在 bootclasspath 中,在9.0 移除了。这个jar包出现在 app PathClassloader DexListPath 中。 tinker 在类 AndroidNClassLoader 方法 recreateDexPathList 在重建 DexPathList的时候有一个判断
//if (!dexFile.getName().equals(baseApkFullPath)) {
//continue;
//}
//这个判断会把原先 PathClassLoader 中的 /system/framework/org.apache.http.legacy.boot.jar 过滤掉。从而导致 在 findClass的时候 加载到打到 apk 内的 org.apache.http.legacy.jar (注意这个只是个头文件) 这个jar本来是只是参与编译的,现在他参与运行的 所以抛出了上述异常。假设没有将 org.apache.http.legacy.jar这个包打到apk内,在 super.findClass的时候会抛异常,tinker 让originClassLoader.loadClass(name) 并且originClassLoader 这个原先的 PathClassLoader 中是有/system/framework/org.apache.http.legacy.boot.jar 。分析到这我也说不清楚算不算tinker的bug了,如果正确使用httpclient的话,是不会有此问题的。以上方案针对targetSdk < 28 。
//最后,请尽快迁移 httpclient。
//https://github.com/Tencent/tinker/issues/957
useLibrary 'org.apache.http.legacy'
在AndroidManifest文件中添加
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
致敬大佬,努力前行!