
导入
在build.gradle下添加依赖,具体版本号参考OkHttp更新日志
dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:4.7.2'
}
简单使用
本文使用的OkHttp版本为4.7.2。OkHttp的核心类主要有OkHttpClient,Dispatcher,Call,Request,Response,Interceptor,Chain。其中OkHttpClient是负责管理多个Call的组织者,而每个Call又包含一个Request和Response,并且Call中的回调用于提供响应结果。要完成一次网络请求,我们需要告诉Call需要处理的Request是什么样的,例如它的URL是什么,然后将Call交给OkHttpClient。OkHttpClient仅对本次请求做一些配置,例如指定缓存路径,它会让Dispatcher去决定何时执行Call。而Dispatcher的底层实现就是一个由OkHttp默认实现的线程池,它将最终执行Call中的.run()方法。最后的Interceptor和Chain将用于数据的拦截处理。OkHttp提供两种方式提交网络请求,分别是Call.execute()和Call.enqueue(Callback),前者会阻塞线程,后者加入队列异步执行。通过调用response.body().string()我们可以得到响应的body部分并以String形式返回,但值得注意的是.string()只能调用一次。
- 同步调用
一般来说要在得到结果的第一时间修改UI,我们可能会使用Call.execute()和AsyncTask完成提交请求。但AsyncTask通常会导致context内存泄漏,因为它是非静态嵌套类,所以不推荐使用同步调用。以下例子使用https://reqres.in测试请求:
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private OkHttpClient mClient = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textView);
String url = "https://reqres.in/api/users/2";
OkHttpHandler okHttpHandler = new OkHttpHandler();
okHttpHandler.execute(url);
}
private class OkHttpHandler extends AsyncTask<String, String, String> {
@Override
protected String doInBackground(String... params) {
Request request = new Request.Builder()
.url(params[0])
.build();
try {
Response response = mClient.newCall(request).execute();
return response.body().string();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mTextView.setText(s);
}
}
}
- 异步调用
抛开同步调用,使用Call.enqueue(Callback)和Activity.runOnUiThread(Runnable)的方式是提交请求的最佳方案。其中Activity.runOnUiThread(Runnable)方法传入Runnable,这个Runnable将插入到UI线程的事件队列末尾,等待执行run()方法。以下例子使用https://reqres.in测试请求:
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private OkHttpClient mClient = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textView);
String url = "https://reqres.in/api/users/2";
try {
get(url);
} catch (IOException e) {
e.printStackTrace();
}
}
private void get(String url) {
Request request = new Request.Builder()
.url(url)
.build();
mClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
call.cancel();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseString = response.body().string();
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(responseString);
}
});
}
});
}
}
使用HttpUrl
HttpUrl用于生成含参的URL,以下例子使用https://resttesttest.com测试请求:
HttpUrl.Builder urlBuilder = HttpUrl.parse("https://httpbin.org/get").newBuilder();
urlBuilder.addQueryParameter("category", "android");
urlBuilder.addQueryParameter("title", "okhttp");
String url2 = urlBuilder.build().toString();
Header处理
- 设置请求头
.header()设置唯一的请求头,旧值会被替换。.addHeader()新增请求头,可以添加多值
Request request = new Request.Builder()
.url(url)
.addHeader("Accept","application/json; charset=utf-8")
.header("Accept","application/json; charset=utf-8")
.post(requestBody)
.build();
- 获得响应头
.header()返回单值,.headers()返回多值的响应头
String headerString=response.header("Server");
List<String> headerStrings=response.headers("Vary");
Log.i(TAG,headerString);
Iterator<String> it=headerStrings.iterator();
while (it.hasNext()) {
Log.i(TAG,it.next());
}
Post提交String
以下例子使用https://reqres.in测试请求:
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String url = "https://reqres.in/api/users/";
String jsonString = "{\n" +
" \"name\": \"morpheus\",\n" +
" \"job\": \"leader\"\n" +
"}";
private void post(String url, final String requestString) {
RequestBody requestBody = RequestBody.create(JSON, requestString);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
mClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
call.cancel();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseString = response.body().string();
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(responseString);
}
});
}
});
}
Post提交表单
final String url = "https://tieba.baidu.com/f";
RequestBody requestBody = new FormBody.Builder()
.add("ie", "utf-8")
.add("kw", "minecraft")
.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Post提交文件
public static final MediaType JPEG = MediaType.parse("image/jpeg");
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM), "building.jpg");
RequestBody requestBody = RequestBody.create(JPEG, file);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Post提交流
RequestBody requestBody = new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return null;
}
@Override
public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
bufferedSink.writeUtf8(requestString);
}
};
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
使用Gson解析response
String url = "https://api.github.com/gists/c2a7c39532239ff261be";
class Gist{
Map<String,GistFile> files;
}
class GistFile{
String content;
}
Gson gson = new Gson();
Gist gist = gson.fromJson(response.body().charStream(),Gist.class);
for(Map.Entry<String,GistFile> entry:gist.files.entrySet()){
Log.i(TAG,entry.getKey()+ " "+entry.getValue().content);
}
设置超时
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(3, TimeUnit.SECONDS)
.build();
配置新client
.newBuilder()会返回一个配置相同的buidler
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(3, TimeUnit.SECONDS)
.build();
OkHttpClient client2 = client.newBuilder()
.connectTimeout(5, TimeUnit.SECONDS)
.build();
拦截器
拦截器(Interceptor)是OkHttp的概念,也是核心功能。OkHttp有两种拦截器,分别是应用拦截器和网络拦截器。拦截器的主要目的在于重写request和response,可以在发出request前修改headers或body,也可以在收到response前修改headers或body。我们完全可以在用户收到reponse前将其修改成一个完全不一样的新response,这一功能使得我们可以进行后续的缓存策略修改或是使用gzip压缩requestBody等操作。应用拦截器在用户发出一次请求后的全过程中仅调用一次,而网络拦截器可能因为重定向等问题多次调用,例如有一次重定向就会调用两次。拦截器可以设置多个,并按添加顺序进行拦截。下图来自OkHttp文档:

两种拦截器区别如下,参考OkHttp文档原文:
个人翻译如下:
- 实现拦截器
以上所说拦截器可对处于中间时期的request和response做修改,就是在chain.proceed(request)的前后完成的。
chain.proceed(request)会返回通过core或服务器处理后得到的response,这个方法会阻塞线程。
String url = "http://publicobject.com/helloworld.txt";
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
//do something to rewrite request
long t1 = System.nanoTime();
Log.i(TAG,String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.i(TAG,String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
//do something to rewrite response
return response;
}
}
- 设置应用拦截器
两种拦截器在实现的时候没有区别,充当那种拦截器取决于调用的方法是.addInterceptor()或是.addNetworkInterceptor()。.addInterceptor()表示设置应用拦截器,.addNetworkInterceptor()则是网络拦截器。
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
- 设置网络拦截器
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
缓存处理
OkHttp默认不使用缓存,可以调用.cache()开启,但.cache()仅能设置缓存区大小和缓存读写的位置。Cache-Control头部是Http协议定义的,而OkHttp完全遵循Http协议,所以OkHttp的缓存策略是由请求头或响应头中的Cache-Control头部而定的。如果服务器返回的response已经带有Cache-Control响应头,在buidler中调用.cache()即可使用缓存。反之当收到的response没有设置Cache-Control时,可以在拦截器里手动添加,不同参数对应不同的缓存策略。不论response是否有Cache-Control,始终可以在发出request时添加例如Cache-control: no-cache来控制缓存使用与否。
启用缓存
String url = "http://publicobject.com/helloworld.txt";
int _10MB = 10 * 1024 * 1024;
File cacheDir = getCacheDir();
Cache cache = new Cache(cacheDir, _10MB);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
缓存策略
Http协议的Cache-Control的参数有很多,可设置多个参数,多个参数间用逗号分隔开。以下主要介绍其中几种的含义
此外与缓存有关的header可能还有Expires和Pragma,这里暂不介绍
- 直接修改Cache-Control头部定义缓存策略
class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
request = request.newBuilder()
.header("Cache-Control", "max-stale=3600")
.build();
return chain.proceed(request);
}
}
Interceptor interceptor = new CacheInterceptor();
mClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(interceptor)
.build();
- 使用CacheControl.Builder()定义缓存策略
CacheControl类只能在拦截器中使用,其实质只是在请求头或响应头为Cache-Control添加不同的参数而已,并没有其他作用
class ForceCacheInterceptor implements Interceptor {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
CacheControl cacheControl = new CacheControl.Builder()
.onlyIfCached()
.build();
request = request.newBuilder()
.cacheControl(cacheControl)
.build();
return chain.proceed(request);
}
}
Interceptor interceptor = new ForceCacheInterceptor();
mClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(interceptor)
.build();
含义参考Cache-Control参数介绍
- 使用CacheControl的伴生对象定义缓存策略
CacheControl的伴生对象有两个,CacheControl.FORCE_CACHE和CacheControl.FORCE_NETWORK,分别表示强制使用缓存和强制使用网络。
public class ForceNetworkInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
if (!NetworkUtils.internetAvailable()) {
builder.cacheControl(CacheControl.FORCE_NETWORK);
}
return chain.proceed(builder.build());
}
}
Interceptor interceptor = new ForceNetworkInterceptor();
mClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(interceptor)
.build();
CacheControl.FORCE_CACHE的本质是将Cache-Control请求头设为"max-stale=2147483647, only-if-cached"。
重新查看上图就可以知道,Application发出的请求是被OkHttp core处理,而OkHttp core发出的请求将提交给服务器,如果我们希望本次请求强制使用缓存,就应该使用应用拦截器而不是网络拦截器,这段请求头告诉OkHttp本次请求仅使用缓存的响应。
CacheControl.FORCE_NETWORK的本质是将Cache-Control请求头设为"no-cache"。与FORCE_CACHE同理,它也应该使用应用拦截器,这段请求头告诉OkHttp本次请求仅使用来自网络的响应。
缓存流程
决定一次请求是否使用缓存的流程,主要的几个步骤如下(任何一步决定使用网络时将不再检查后续步骤)
检查
response是否包含Date,Expires,Last-Modified,ETag,Age这些请求头。若都不包含则使用网络。检查
request的Cache-Control请求头,"no-cache"使用网络,"only-if-cached"使用缓存。检查
response的ETag响应头,若存在则使用网络,并且本次请求会带有与ETag值相同的If-None-Match请求头。若实际数据没有变化,服务器处理后会给出304 Not Modified状态码,表示资源没有修改,并且不会返回body,指示客户端使用缓存,所以此时OkHttp也会使用缓存。检查
response的Last-Modified响应头,若存在则使用网络,并且本次请求会带有与Last-Modified值相同的If-Modified-Since请求头。后续同ETag。检查
response的Date响应头,若存在则使用网络,并且本次请求会带有与Date值相同的If-Modified-Since请求头。后续同ETag。检查
response的max-age,如果过期则使用网络,否则使用缓存。但也可能因为其他参数如max-stale等影响最终计算结果。
缓存总结
一次完整的涉及缓存的网络请求大致如下图,其中成功的结果有两个(绿框),分别是使用缓存和使用服务器的新数据。在Force cache后找不到缓存就会失败(红框)。从初始阶段向下看,第一步判断是否调用.cache()开启了缓存功能。第二步检查之前是否缓存过,两者任意一者不满足则使用网络。第三步判断是否需要验证,与ETag等有关,存在则使用网络向服务器验证,服务器若返回304则response完全从缓存中取出。这步操作同普通请求一样,可能涉及无网络问题。当无网络时可以Force cache进行处理,最后则是成功或失败时的异常处理。下图来自Medium

- 当存储缓存时
如果此时要修改response的头部,应该使用网络拦截器修改response。
只要在构建client的时候调用了.cache(),那么通过这个client得到的响应一定会被缓存,但之后不一定会被使用。存储缓存时与Cache-Control请求头或响应头都无关,Cache-Control只有当读取缓存时才会用到。
- 当读取缓存时
应该使用应用拦截器修改request。
想要强制使用缓存,有以下3种方式:
但如果缓存不存在,这次请求就会失败并抛出IOException,并且得到一个带有504 Gateway Timeout的response。
想要强制使用网络,有以下3种方式:
如果你在请求头没有指定任何有关缓存的参数,OkHttp将按照缓存中response的数个响应头进行不同的处理,可能使用缓存,也可能向服务器验证response后决定是否使用缓存,或是进行一次普通的请求。
最后
配合各种资料辅助学习
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2020最新上万页的大厂面试真题

七大模块学习资料:如NDK模块开发、Android框架体系架构...

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
复制链接查看隐藏内容:https://shimo.im/docs/QdyGqGHXX8PyQ8pw
最后
有段话想分享给大家:
“如果你热爱,那么请继续热爱,你的付出终将获得与之匹配的回报,如果眼前觉得没有希望,不妨再坚持一会,‘今天很残酷,明天也很残酷,但是后天很美好’ ‘冬天都已经来了,春天还会远吗?’ ”
道理就是这个道理,但是“大道理大家都懂”,而那些成功的人,就是把这些道理运用到了工作和生活当中。
共勉!










