OkHttp
请求器和请求对象
使用OkHttp发送网络请求,最重要的是OkHttpClient和Request这两个类,前者是请求器,后者是请求对象。
1 2 3 4
| OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request.Builder builder = new Request.Builder();
|
确定请求地址和请求方式后,再构建出请求对象,如下示例构建get请求对象
1 2 3
| Request request = builder.url("https://m1.apifoxmock.com/m1/8092681-7849123-default/get/common") .get() .build();
|
发送get请求
通过请求器发送请求对象,得到一个Call对象,得到Call对象后可以选择执行同步请求或异步请求。
1 2 3 4 5
| Request request = builder.url("https://m1.apifoxmock.com/m1/8092681-7849123-default/get/common") .get() .build();
Call call = okHttpClient.newCall(request);
|
同步请求
同步请求会阻塞当前线程,直到请求完成,因此无法在主线程中执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| new Thread(() -> { try (Response response = call.execute()) { if (response.isSuccessful()) { LogUtil.d(TAG, "请求成功,code=" + response.code()); ResponseBody responseBody = response.body(); if (responseBody != null) { String result = responseBody.string(); LogUtil.d(TAG, "response: " + result); } else { LogUtil.d(TAG, "response=null"); } } else { LogUtil.d(TAG, "请求失败,code=" + response.code()); } } catch (IOException e) { throw new RuntimeException(e); } }).start();
|
异步请求
异步请求不阻塞当前线程,发送异步请求需要传入一个Callback对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| call.enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { LogUtil.d(TAG, "onFailure: " + e.getMessage()); } @Override public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { if (response.isSuccessful()) { LogUtil.d(TAG, "请求成功,code=" + response.code()); ResponseBody responseBody = response.body(); if (responseBody != null) { String result = responseBody.string(); LogUtil.d(TAG, "onResponse: response=" + result); } else { LogUtil.d(TAG, "onResponse: response=null"); } } else { LogUtil.d(TAG, "请求失败,code=" + response.code()); } response.close(); } });
|
文件下载
对Response对象执行如下操作,即可实现文件下载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| if (response.isSuccessful()) { LogUtil.d(TAG, "请求成功,code=" + response.code()); ResponseBody responseBody = response.body(); if (responseBody != null) { long totalBytes = responseBody.contentLength(); File file = new File("/storage/emulated/0/pig.png"); try (InputStream inputStream = responseBody.byteStream(); FileOutputStream outputStream = new FileOutputStream(file)) { byte[] buffer = new byte[4096]; int len; long downloaded = 0; while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); downloaded += len; int percent = (int) (downloaded * 100L / totalBytes); LogUtil.d(TAG, "下载进度: " + percent + "% (" + downloaded + "/" + totalBytes + " bytes)"); } outputStream.flush(); } LogUtil.d(TAG, "文件下载成功"); } else { LogUtil.d(TAG, "onResponse: response=null"); } } else { LogUtil.d(TAG, "请求失败,code=" + response.code()); } response.close();
|
发送post请求
发送post请求,在构建Request对象时,需要传入一个RequestBody对象,如下示例发送json数据
1 2 3 4 5 6 7 8
| String json = "{ \"email\": \"x@xxin.xyz\", \"password\": \"123456\"}"; RequestBody requestBody = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
Request request = builder.url("https://m1.apifoxmock.com/m1/8092681-7849123-default/post/login") .post(requestBody) .build(); Call call = okHttpClient.newCall(request);
|
发送表单数据
1 2 3 4 5 6 7 8
| RequestBody requestBody = new FormBody.Builder() .add("email", "x@xxin.xyz") .add("password", "123456") .build(); Request request = builder.url("https://m1.apifoxmock.com/m1/8092681-7849123-default/post/login") .post(requestBody) .build(); Call call = okHttpClient.newCall(request);
|
同步、异步请求的发送方式与get请求相同,不再赘述
文件上传
单文件上传
1 2 3 4 5 6 7
| File file = new File("/storage/emulated/0/test.txt"); RequestBody requestBody = RequestBody.create(file, MediaType.parse("application/octet-stream"));
Request request = builder.url("https://www.httpbin.org/post") .post(requestBody) .build(); Call call = okHttpClient.newCall(request);
|
多文件上传
1 2 3 4 5 6 7 8 9
| MultipartBody multipartBody = new MultipartBody.Builder() .addFormDataPart("file1", file1.getName(), requestBody1) .addFormDataPart("file2", file2.getName(), requestBody2) .build();
Request request = builder.url("https://www.httpbin.org/post") .post(multipartBody) .build(); Call call = okHttpClient.newCall(request);
|
带进度的文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| File file = new File("/storage/emulated/0/test.txt"); RequestBody fileBody = RequestBody.create(file, MediaType.parse("application/octet-stream")); RequestBody requestBody = new ProgressRequestBody(fileBody, (written, total) -> { if (total > 0) { int percent = (int) (written * 100L / total); LogUtil.d(TAG, "上传进度: " + percent + "% (" + written + "/" + total + " bytes)"); } else if (total == 0) { LogUtil.d(TAG, "上传进度: 空文件"); } else { LogUtil.d(TAG, "上传进度: 文件错误"); } }); Request request = builder.url("https://www.httpbin.org/post") .post(requestBody) .build(); Call call = okHttpClient.newCall(request);
|
重写RequestBody,在写入数据时统计已写入字节数,并通过回调接口通知上传进度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public final class ProgressRequestBody extends RequestBody { private final RequestBody delegate; private final ProgressListener progressListener;
public ProgressRequestBody(RequestBody delegate, ProgressListener progressListener) { this.delegate = delegate; this.progressListener = progressListener; }
@Override public MediaType contentType() { return delegate.contentType(); }
@Override public long contentLength() throws IOException { return delegate.contentLength(); }
@Override public void writeTo(@NonNull BufferedSink sink) throws IOException { CountingSink countingSink = new CountingSink(sink); BufferedSink progressSink = Okio.buffer(countingSink); delegate.writeTo(progressSink); progressSink.flush(); } private class CountingSink extends ForwardingSink { private long bytesWritten = 0; private long contentLength = -1; public CountingSink(BufferedSink delegate) { super(delegate); } @Override public void write(@NonNull Buffer source, long byteCount) throws IOException { super.write(source, byteCount); if (contentLength == -1) { contentLength = contentLength(); } bytesWritten += byteCount; if (progressListener != null) { progressListener.onProgress(bytesWritten, contentLength); } } } public interface ProgressListener { void onProgress(long bytesWritten, long totalBytes); } }
|
拦截器
Okhttp的拦截器采用责任链模式,每个拦截器通过chain.proceed(Request)把请求(Request对象)交给责任链中的下个拦截器处理,chain.proceed(Request)可以得到下个拦截器返回的响应(Response对象),并且需要把响应返回给上个拦截器。
OkHttp拦截器责任链的完整执行顺序如下

应用拦截器
应用拦截器可以添加多个,先添加的拦截器先处理请求,但最后处理响应。对于日志、统一添加Header等不关心网络中间过程的场景,优先使用应用拦截器,对于需要观察网络重定向、处理响应压缩等场景,考虑使用网络拦截器
如下拦截器,用于在请求发送之前添加统一的请求头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class HeaderInterceptor implements Interceptor { @NonNull @Override public Response intercept(@NonNull Chain chain) throws IOException { Request originalRequest = chain.request(); Request newRequest = originalRequest.newBuilder() .header("Authorization", "test authorization") .addHeader("Custom-Header", "test custom header") .build(); return chain.proceed(newRequest); } }
|
如下拦截器,用于记录请求耗时和输出响应体内容
ResponseBody的数据流只能被消费一次,若需多次读取,可用response.peekBody()获取一个副本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class LoggingInterceptor implements Interceptor { private static final String TAG = LoggingInterceptor.class.getSimpleName(); @NonNull @Override public Response intercept(@NonNull Chain chain) throws IOException { Request request = chain.request(); long startTime = System.nanoTime(); Response response = chain.proceed(request); long duration = System.nanoTime() - startTime; LogUtil.d(TAG, "请求耗时: " + duration); ResponseBody peekBody = response.peekBody(Long.MAX_VALUE); LogUtil.d(TAG, "Body: " + peekBody.string()); return response; } }
|
添加拦截器到OkHttpClient中
1 2 3 4
| OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new HeaderInterceptor()) .addInterceptor(new LoggingIntercepter()) .build();
|
网络拦截器
Volley
Retrofit
Retrofit本身不执行网络请求,只负责封装请求和解析结果,真正的网络通信由OkHttp负责
发送get请求
先根据API返回的json数据结构