一、原理
基于原生Http。
二、特点
- 支持同步、异步。
- 支持GZIP减少数据流量。
- 缓存响应数据,减少重复的网络请求。
- 自动重连,处理了代理服务器问题和SSL握手失败问题。
- 支持SPDY,共享同个Socket来处理同一服务器的请求,若SPDY不可用则通过连接池来减少请求延时。
- 请求、处理速度快,基于NIO(非阻塞式)、Okio(Square基于IO、NIO的一个高效处理数据流的开源库)。
三、使用场景
数据量大的重量级网络请求。
四、Call模型
- Http客户端的任务是处理请求和响应。
- Http请求:包含一个URL、请求方法、请求头,还可能包含请求体,请求体可以是数据流也可以是指定的内容类别。
- Http响应:包含响应码、响应头和响应体。
1.请求的重写
为了保证正确性和传输效率,OkHttp会在发送请求之前重写它:
- 添加原始请求中缺失的头信息,包括Content-Length、Transfer-Encoding、User-Agent、Host、Connection和Content-Type。
- 为了实现透明的响应压缩,会增加Accept-Encoding头信息。
- 如果收到了cookie,会增加cookie头信息。
- 某些请求可能会对响应做缓存,如果被缓存的响应不是最新的,能做一个有条件的GET请求来下载更新后的响应,此功能需要添加If-Modified-Since和If-None-Match等头信息。
2.响应的重写
- 如果使用了透明压缩,OkHttp会去掉对应响应的Content-Encoding和Content-Length头信息,因为它们不能应用于解压后的响应体。
- 如果有条件的GET请求成功了,网络侧的响应和缓存的响应会被自动合并。
3.重定向
如果请求的URL被重定向了,服务器会返回类似于302这样的响应码,来指明新的URL,OkHttp能跟随新的URL,获取到最终的响应。
4.请求的重试
有时网络连接状况不好或者服务器不可达,会发生连接失败,OkHttp会自动使用不同的路由来重试请求。
5.Call模型
由于重写、重定向和重试等操作,一个简单请求可能会产生多个请求和响应,OkHttp使用了Call模型,为了满足请求任务,不论中间做了多少次请求和响应,都算作一个Call。
Call有两种方式来执行,同步方式和异步方式,由于OkHttp是一个Java库,不是Android库,它对Android主线程一无所知,所以异步回调方法是在后台线程执行的。
五、使用方法
添加依赖及网络权限
//build.gradle
implementation 'com.squareup.okhttp3:okhttp:3.2.0'
//AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
1.同步Get请求
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
try {
request();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void request() throws IOException {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code: " + response);
}
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
Log.d(TAG, "zwm, response header, name: " + responseHeaders.name(i) + ", value: " + responseHeaders.value(i));
}
Log.d(TAG, "zwm, response body: " + response.body().string());
}
}
//输出log
2019-12-26 11:23:47.872 zwm, onCreate
2019-12-26 11:23:47.874 zwm, request, thread: Thread-6
2019-12-26 11:23:48.428 zwm, response header, name: Cache-Control, value: private, no-cache, no-store, proxy-revalidate, no-transform
2019-12-26 11:23:48.428 zwm, response header, name: Connection, value: keep-alive
2019-12-26 11:23:48.428 zwm, response header, name: Content-Type, value: text/html
2019-12-26 11:23:48.428 zwm, response header, name: Date, value: Thu, 26 Dec 2019 03:23:36 GMT
2019-12-26 11:23:48.430 zwm, response header, name: Last-Modified, value: Mon, 23 Jan 2017 13:24:18 GMT
2019-12-26 11:23:48.430 zwm, response header, name: Pragma, value: no-cache
2019-12-26 11:23:48.430 zwm, response header, name: Server, value: bfe/1.0.8.18
2019-12-26 11:23:48.430 zwm, response header, name: Set-Cookie, value: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
2019-12-26 11:23:48.430 zwm, response header, name: Transfer-Encoding, value: chunked
2019-12-26 11:23:48.430 zwm, response header, name: OkHttp-Sent-Millis, value: 1577330628379
2019-12-26 11:23:48.430 zwm, response header, name: OkHttp-Received-Millis, value: 1577330628423
2019-12-26 11:23:48.433 zwm, response body: <!DOCTYPE html>
...
响应体的string()方法对于小文件来说非常方便和高效,但如果响应体较大(大于1M),要避免使用这一方法,因为它会将文件内容全部加载在内存上,在这种情况下,使用流来处理响应体。
2.异步Get请求
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
Log.d(TAG, "zwm, response header, name: " + responseHeaders.name(i) + ", value: " + responseHeaders.value(i));
}
Log.d(TAG, "zwm, response body: " + response.body().string());
}
});
}
}
//输出log
2019-12-26 11:35:11.880 zwm, onCreate
2019-12-26 11:35:11.885 zwm, request, thread: Thread-6
2019-12-26 11:35:12.676 zwm, onResponse, thread: OkHttp https://www.baidu.com/
2019-12-26 11:35:12.676 zwm, response header, name: Cache-Control, value: private, no-cache, no-store, proxy-revalidate, no-transform
2019-12-26 11:35:12.676 zwm, response header, name: Connection, value: keep-alive
2019-12-26 11:35:12.677 zwm, response header, name: Content-Type, value: text/html
2019-12-26 11:35:12.677 zwm, response header, name: Date, value: Thu, 26 Dec 2019 03:35:00 GMT
2019-12-26 11:35:12.677 zwm, response header, name: Last-Modified, value: Mon, 23 Jan 2017 13:24:18 GMT
2019-12-26 11:35:12.677 zwm, response header, name: Pragma, value: no-cache
2019-12-26 11:35:12.677 zwm, response header, name: Server, value: bfe/1.0.8.18
2019-12-26 11:35:12.677 zwm, response header, name: Set-Cookie, value: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
2019-12-26 11:35:12.677 zwm, response header, name: Transfer-Encoding, value: chunked
2019-12-26 11:35:12.677 zwm, response header, name: OkHttp-Sent-Millis, value: 1577331312637
2019-12-26 11:35:12.677 zwm, response header, name: OkHttp-Received-Millis, value: 1577331312672
2019-12-26 11:35:12.682 zwm, response body: <!DOCTYPE html>
...
3.Http头信息
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
Request request = new Request.Builder()
.url("https://www.baidu.com")
.header("User-Agent", "OkHttp User-Agent")
.addHeader("Accept", "text/html")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
Log.d(TAG, "zwm, Server: " + response.header("Server"));
Log.d(TAG, "zwm, Date: " + response.header("Date"));
Log.d(TAG, "zwm, Vary: " + response.headers("Vary"));
}
});
}
}
//输出log
2019-12-26 13:17:07.047 zwm, onCreate
2019-12-26 13:17:07.048 zwm, request, thread: Thread-6
2019-12-26 13:17:08.380 zwm, onResponse, thread: OkHttp https://www.baidu.com/
2019-12-26 13:17:08.380 zwm, Server: BWS/1.1
2019-12-26 13:17:08.381 zwm, Date: Thu, 26 Dec 2019 05:16:56 GMT
2019-12-26 13:17:08.381 zwm, Vary: []
4.Post字符串
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//输出log
2019-12-26 13:25:26.779 zwm, onCreate
2019-12-26 13:25:26.782 zwm, request, thread: Thread-6
2019-12-26 13:25:28.471 zwm, onResponse, thread: OkHttp https://api.github.com/markdown/raw
2019-12-26 13:25:28.497 zwm, body: <h2>
...
不能使用该方法传送大文件(大于1M)。
5.Post流
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override public void writeTo(BufferedSink sink) throws IOException { //使用Okio的buffered sink
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//输出log
2019-12-26 13:30:49.632 zwm, onCreate
2019-12-26 13:30:49.648 zwm, request, thread: Thread-6
2019-12-26 13:30:53.491 zwm, onResponse, thread: OkHttp https://api.github.com/markdown/raw
2019-12-26 13:30:53.499 zwm, body: <h2>
...
6.Post文件
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
File dir = getExternalCacheDir();
if(!dir.exists()) {
Log.d(TAG, "zwm, dir not exist, mkdir");
dir.mkdir();
return;
}
File file = new File(dir.getAbsolutePath(), "application123.json");
if(!file.exists()) {
Log.d(TAG, "zwm, file not exist");
return;
}
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//输出log
2019-12-26 13:43:04.978 zwm, onCreate
2019-12-26 13:43:04.979 zwm, request, thread: Thread-6
2019-12-26 13:43:10.758 zwm, onResponse, thread: OkHttp https://api.github.com/markdown/raw
2019-12-26 13:43:10.762 zwm, body: <p>{
...
7.Post form参数
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
RequestBody formBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
8.Post多部分请求
MultipartBody.Builder可以构建复杂的请求体,多部分请求体的每一个部分都是一个单一的请求体,可以定义它自身的请求头。
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private final OkHttpClient client = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
9.使用Gson来解析JSON响应
可以使用Gson库来解析服务器响应的JSON数据,其中ResponseBody.charStream()方法使用响应头中的Content-Type字段来选择使用何种字符集,如果没有指明,默认将使用UTF-8。
//添加Gson依赖:
implementation 'com.google.code.gson:gson:2.6.2'
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final OkHttpClient client = new OkHttpClient();
private final Gson gson = new Gson();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
Request request = new Request.Builder()
.url("https://api.github.com/gists/c2a7c39532239ff261be")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
Log.d(TAG, "zwm, key: " + entry.getKey());
Log.d(TAG, "zwm, value.content: " + entry.getValue().content);
}
}
});
}
static class Gist {
Map<String, GistFile> files;
}
static class GistFile {
String content;
}
}
//输出log
2019-12-26 14:00:08.963 zwm, onCreate
2019-12-26 14:00:08.966 zwm, request, thread: Thread-6
2019-12-26 14:00:12.509 zwm, onResponse, thread: OkHttp https://api.github.com/gists/c2a7c39532239ff261be
2019-12-26 14:00:12.560 zwm, key: OkHttp.txt
2019-12-26 14:00:12.560 zwm, value.content:
...
10.Call的取消
使用Call.cancel()来停止一个执行中的请求,如果线程正在写请求或读响应,则会收到IOException。使用此方法,在一个Call已经不需要时取消它,可以节省网络流量。
11.使用缓存
基本使用
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
//创建缓存对象
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
Log.d(TAG, "zwm, cache: " + response.cacheResponse());
Log.d(TAG, "zwm, network: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "zwm, request2, thread: " + Thread.currentThread().getName());
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread2: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread2: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body2: " + response.body().string());
Log.d(TAG, "zwm, cache2: " + response.cacheResponse());
Log.d(TAG, "zwm, network2: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//输出log
2019-12-26 14:19:14.379 zwm, onCreate
2019-12-26 14:19:14.380 zwm, request, thread: Thread-6
2019-12-26 14:19:16.295 zwm, onResponse, thread: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:19:16.347 zwm, body:
...
2019-12-26 14:19:16.347 zwm, cache: null
2019-12-26 14:19:16.348 zwm, network: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 14:19:19.803 zwm, request2, thread: Thread-6
2019-12-26 14:19:20.087 zwm, onResponse, thread2: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:19:20.088 zwm, body2:
...
2019-12-26 14:19:20.088 zwm, cache2: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 14:19:20.088 zwm, network2: null
使用CacheControl(推荐)
CacheControl是针对Request的,可以针对每个请求设置不同的缓存策略。
CacheControl.Builder常用方法:
- noCache() //不使用缓存,用网络请求
- noStore() //不使用缓存,也不存储缓存
- onlyIfCached() //只使用缓存
- noTransform() //禁止转码
- maxAge(10, TimeUnit.MILLISECONDS) //设置超时时间为10ms
- maxStale(10, TimeUnit.SECONDS) //超时时间加上10s
- minFresh(10, TimeUnit.SECONDS) //超时时间减去10s
设置一个10秒的超时时间:
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
//创建缓存对象
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
//设置缓存时间为10秒
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(10, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(cacheControl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
Log.d(TAG, "zwm, cache: " + response.cacheResponse());
Log.d(TAG, "zwm, network: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "zwm, request2, thread: " + Thread.currentThread().getName());
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread2: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread2: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body2: " + response.body().string());
Log.d(TAG, "zwm, cache2: " + response.cacheResponse());
Log.d(TAG, "zwm, network2: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//输出log
2019-12-26 14:41:10.258 zwm, onCreate
2019-12-26 14:41:10.258 zwm, request, thread: Thread-6
2019-12-26 14:41:12.581 zwm, onResponse, thread: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:41:12.593 zwm, body:
...
2019-12-26 14:41:12.593 zwm, cache: null
2019-12-26 14:41:12.593 zwm, network: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 14:41:30.599 zwm, request2, thread: Thread-6
2019-12-26 14:41:31.131 zwm, onResponse, thread2: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:41:31.132 zwm, body2:
...
2019-12-26 14:41:31.133 zwm, cache2: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 14:41:31.133 zwm, network2: Response{protocol=http/1.1, code=304, message=Not Modified, url=https://publicobject.com/helloworld.txt}
强制使用缓存:
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
//创建缓存对象
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
//设置缓存时间为10秒
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(10, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(cacheControl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
Log.d(TAG, "zwm, cache: " + response.cacheResponse());
Log.d(TAG, "zwm, network: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "zwm, request2, thread: " + Thread.currentThread().getName());
request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(CacheControl.FORCE_CACHE)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread2: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread2: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body2: " + response.body().string());
Log.d(TAG, "zwm, cache2: " + response.cacheResponse());
Log.d(TAG, "zwm, network2: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//输出log
2019-12-26 14:50:50.155 zwm, onCreate
2019-12-26 14:50:50.156 zwm, request, thread: Thread-6
2019-12-26 14:50:53.710 zwm, onResponse, thread: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:50:53.753 zwm, body:
...
2019-12-26 14:50:53.754 zwm, cache: null
2019-12-26 14:50:53.755 zwm, network: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 14:51:10.437 zwm, request2, thread: Thread-6
2019-12-26 14:51:10.467 zwm, onResponse, thread2: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:51:10.468 zwm, body2:
...
2019-12-26 14:51:10.468 zwm, cache2: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 14:51:10.468 zwm, network2: null
不使用缓存:
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
//创建缓存对象
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
//设置缓存时间为10秒
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(10, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(cacheControl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
Log.d(TAG, "zwm, cache: " + response.cacheResponse());
Log.d(TAG, "zwm, network: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "zwm, request2, thread: " + Thread.currentThread().getName());
request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread2: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread2: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body2: " + response.body().string());
Log.d(TAG, "zwm, cache2: " + response.cacheResponse());
Log.d(TAG, "zwm, network2: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//输出log
2019-12-26 14:52:53.590 zwm, onCreate
2019-12-26 14:52:53.590 zwm, request, thread: Thread-6
2019-12-26 14:52:56.634 zwm, onResponse, thread: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:52:56.664 zwm, body:
...
2019-12-26 14:52:56.665 zwm, cache: null
2019-12-26 14:52:56.665 zwm, network: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 14:53:13.885 zwm, request2, thread: Thread-6
2019-12-26 14:53:14.831 zwm, onResponse, thread2: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 14:53:14.837 zwm, body2:
...
2019-12-26 14:53:14.838 zwm, cache2: null
2019-12-26 14:53:14.838 zwm, network2: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
maxAge设置为0,发起网络请求执行对比缓存:
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
//创建缓存对象
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
//设置缓存时间为10秒
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(10, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(cacheControl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
Log.d(TAG, "zwm, cache: " + response.cacheResponse());
Log.d(TAG, "zwm, network: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "zwm, request2, thread: " + Thread.currentThread().getName());
cacheControl = new CacheControl.Builder()
.maxAge(0, TimeUnit.SECONDS)
.build();
request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(cacheControl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread2: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread2: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body2: " + response.body().string());
Log.d(TAG, "zwm, cache2: " + response.cacheResponse());
Log.d(TAG, "zwm, network2: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
使用缓存拦截器(不推荐)
如果服务器能在返回消息的时候添加好Cache-Control相关的消息头,那么客户端就能够正常使用缓存,但是如果服务器无法配合客户端添加Cache-Control相关的消息头的话,那么客户端要想使用缓存可以添加缓存拦截器,人为地添加响应消息中Cache-Control相关的消息头,然后再传递给用户。
//测试代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, onCreate");
new Thread(new Runnable() {
@Override
public void run() {
request();
}
}).start();
}
private void request() {
Log.d(TAG, "zwm, request, thread: " + Thread.currentThread().getName());
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
//创建缓存对象
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new CacheInterceptor())
.cache(cache)
.build();
//设置缓存时间为10秒
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(10, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(cacheControl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body: " + response.body().string());
Log.d(TAG, "zwm, cache: " + response.cacheResponse());
Log.d(TAG, "zwm, network: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "zwm, request2, thread: " + Thread.currentThread().getName());
cacheControl = new CacheControl.Builder()
.build();
request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.cacheControl(cacheControl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "zwm, onFailure, thread2: " + Thread.currentThread().getName());
}
@Override
public void onResponse(Call call, Response response) {
Log.d(TAG, "zwm, onResponse, thread2: " + Thread.currentThread().getName());
try {
Log.d(TAG, "zwm, body2: " + response.body().string());
Log.d(TAG, "zwm, cache2: " + response.cacheResponse());
Log.d(TAG, "zwm, network2: " + response.networkResponse());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
static class CacheInterceptor implements Interceptor {
private static final String TAG = "CacheInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
Log.d(TAG, "zwm, intercept");
Response originResponse = chain.proceed(chain.request());
//设置缓存时间为60秒,并移除了pragma消息头,移除它的原因是因为pragma也是控制缓存的一个消息头属性
return originResponse.newBuilder()
.removeHeader("pragma")
.header("Cache-Control","max-age=60")
.build();
}
}
}
//输出log
2019-12-26 15:13:26.326 zwm, onCreate
2019-12-26 15:13:26.327 zwm, request, thread: Thread-6
2019-12-26 15:13:26.628 zwm, intercept
2019-12-26 15:13:28.764 zwm, intercept
2019-12-26 15:13:29.011 zwm, onResponse, thread: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 15:13:29.034 zwm, body:
...
2019-12-26 15:13:29.034 zwm, cache: null
2019-12-26 15:13:29.034 zwm, network: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 15:13:36.609 zwm, request2, thread: Thread-6
2019-12-26 15:13:36.645 zwm, onResponse, thread2: OkHttp http://publicobject.com/helloworld.txt
2019-12-26 15:13:36.646 zwm, body2:
...
2019-12-26 15:13:36.646 zwm, cache2: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2019-12-26 15:13:36.647 zwm, network2: null
12.设置超时
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
.writeTimeout(10, TimeUnit.SECONDS) // Socket写超时
.readTimeout(30, TimeUnit.SECONDS) // Socket读超时
.build();
在2.5.0版本之后,读、写、连接超时的默认值是10s。