OKHttp是处理网络请求的开源框架,Andorid当前最火热的网络框架,Retrofit的底层也是OKHttp,用于替换HttpUrlConnection和Apache HttpClient(API23 6.0已经移除)。
概况起来说OKHttp是一款优秀HTTP框架,它支持GET和POST请求,支持Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟的问题。
OKHttp的优点:
1.支持HTTP2/SPDY,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接。
2.如果HTTP2/SPDY不可用OkHttp,会使用连接池来复用连接以提高效率。
3.提供了对 GZIP 的默认支持来降低传输内容的大小
4.提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求
5.当网络出现问题时,OkHttp会自动重试一个主机的多个 IP 地址
(1)首先在清单文件AndroidManifest.xml中添加网络权限
(2)在build.gradle文件中添加okhttp依赖:
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
(1)创建OkHttpClient实例
(2)通过Builder辅助类构建Request请求对象
(3)OkHttpClient实例回调请求,得到Call对象
(4)同步/异步执行请求,获取Response对象
(1)创建OkHttpClient实例
//方式一:创建OkHttpClient实例,使用默认构造函数,创建默认配置OkHttpClient(官方建议全局只有一个实例)
OkHttpClient okHttpClient = new OkHttpClient();
//方式二:通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例
OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();
//方式三:如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造
OkHttpClient client = okHttpClient.newBuilder().build();
使用默认构造函数创建OkHttpClient实例,创建默认配置OkHttpClient(官方建议全局只有一个实例)。也可以通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例,如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造。
(2)超时设置
我们在创建OkHttpClient实例的时候也会设置相关的属性,通过.Builder().build()的形式设置,比如超时时间设置:
//1.构建OkHttpClient实例
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(2, TimeUnit.SECONDS)//链接超时为2秒,单位为秒
.writeTimeout(2, TimeUnit.SECONDS)//写入超时
.readTimeout(2, TimeUnit.SECONDS)//读取超时
.build();
//2.通过Builder辅助类构建请求对象
final Request request = new Request.Builder()
.url("http://httpbin.org/delay/10")//URL地址
.build();//构建
//创建线程,在子线程中运行
new Thread(new Runnable() {
@Override
public void run() {
try {
//3.通过mOkHttpClient调用请求得到Call
final Call call = okHttpClient.newCall(request);
//4.执行同步请求,获取响应体Response对象
Response response = call.execute();
Log.e(TAG, "请求(超时)==" + response);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "请求(超时)==" + e.toString());
}
}
}).start();
我这里设置了请求10秒后才请求成功,请求超时时间为2秒,读写超时时间为2秒,网络请求是耗时的请求操作,需要另外开子线程运行,抛出了超时异常,测试一下效果:
这里引出了http请求的几个重要的角色,Request是OKHttp的访问请求,Builder是访问辅助类,Response是OKHttp的请求响应
Request(请求):每一个HTTP请求中,都应该包含一个URL,一个GET或POST方法,以及Header和其他参数,还可以包含特定内容类型的数据流。
Responses(响应):响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。
另外可以根据response.code()获取返回的状态码。OKHttp:简单来说,通过OkHttpClient可以发送一个http请求,并且可以读取该请求的响应,它是一个生产Call的工厂。收益于一个共享的响应缓存/线程池/复用的链接等因素,绝大多数应用使用一个OKHttpClient实例,便可满足整个应用的Http请求
(3)HTTP头部的设置和读取
HTTP头部的数据结构是Map
OKHTTP的处理方式是:
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")//设置唯一值
.addHeader("Server", "application/json; q=0.5")//设置新值
.addHeader("Server", "application/vnd.github.v3+json")//设置新值
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post请求(HTTP头)异步响应failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Log.e(TAG, "header:Date==" + response.header("Date"));
Log.e(TAG, "header:User-Agent==" + response.header("User-Agent"));
Log.e(TAG, "headers:Server==" + response.headers("Server"));
Log.e(TAG, "headers:Vary==" + response.headers("Vary"));
Log.e(TAG, "Post请求(HTTP头)异步响应Success==" + response.body().string());
}
});
在Request请求中通过.Builder().build()的形式设置url(请求地址),也可以设置该请求的头部信息,response的body有很多种输出方法,string()只是其中之一,注意是string()不是toString()。如果是下载文件就是response.body().bytes()。我们来看看打印出来的结果:
(4)GET请求(同步)
new Thread(new Runnable() {
@Override
public void run() {
//通过Builder辅助类构建请求对象
Request request = new Request.Builder()
.get()//get请求
.url("https://www.baidu.com")//请求地址
.build();//构建
try {
//通过mOkHttpClient调用请求得到Call
final Call call = mOkHttpClient.newCall(request);
//执行同步请求,获取Response对象
Response response = call.execute();
if (response.isSuccessful()) {//如果请求成功
String string = response.body().string();
Log.e(TAG, "get同步请求success==" + string);
//响应体的string()对于小文档来说十分方便高效,但是如果响应体太大(超过1M),应避免使用string()方法,
//因为它会把整个文档加载到内存中,对用超多1M的响应body,应该使用流的方式来处理。
//response.body().bytes();//字节数组类型
//response.body().byteStream();//字节流类型
//response.body().charStream();//字符流类型
printHeads(response.headers());
} else {
Log.e(TAG, "get同步请求failure==");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
/**
* 打印请求头信息
*
* @param headers 请求头集合
*/
private void printHeads(Headers headers) {
if (headers == null) return;
for (int i = 0; i < headers.size(); i++) {
Log.e(TAG, "请求头==" + headers.name(i) + ":" + headers.value(i));
}
}
在请求Request中声明为GET请求,设置url请求地址,调用实例mOkHttpClient.new Call(request)调用请求,返回Call,通过同步方法call.execute()同步执行,这里需要开启一个子线程运行。需要手动通过response.isSuccessful()判断请求是否成功,同时通过响应体得到Heander打印了请求头的相关信息,看看得出的结果:
(5)GET请求(异步)
同步和异步不一样的地方是Call对象调用的方法,call.enqueue(callback)实现函数回调成功和失败两方法,异步方法可以不用开启子线程执行。
注意:同步是阻塞式的,串联执行,同步发生在当前线程内。异步是并发式的,会再次创建子线程处理耗时操作。
//通过Builder辅助类构建Request对象,链式编程
Request request = new Request.Builder()
.url("https://www.baidu.com")
.get()
.build();
//异步
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
Log.e(TAG, "get异步响应失败==" + e.toString());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//主线程中更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
//TODO 在主线程中更新UI的操作
}
});
Log.e(TAG, "get异步当前线程,线程id==" + Thread.currentThread().getId());
String result = response.body().string();
Log.e(TAG, "get异步响应成功==" + result);
printHeads(response.headers());
}
});
Log.e(TAG, "主线程,线程id==" + Thread.currentThread().getId());
打印log如下:
onFailure()和onResponse()分别是在请求失败和成功时会调用的方法。这里有个要注意的地方,onFailure()和onResponse()是在异步线程里执行的,所以如果你在Android把更新UI的操作写在这两个方法里面是会报错的,这个时候可以用runOnUiThread这个方法进行更新UI操作。
(6)POST提交String请求(同步)
post请求创建request和get是一样的,只是post请求需要提交一个表单,就是RequestBody。表单的格式有好多种,post请求提交参数需要构建RequestBody对象,post提交的过程需要将提交的内容封装到一个RequestBody中,同时需要设置类型MediaType,MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type,通过.Builder().build()的形式设置URL(请求地址),RequestBody(参数容器)。
//构建RequestBody对象,post提交的过程需要将提交的内容封装到一个RequestBody中
//MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type
MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
RequestBody requestBody = RequestBody.create("提交的内容", mediaType);
final Request request = new Request.Builder()
.post(requestBody)
.url("https://api.github.com/markdown/raw")
.build();
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
Log.e(TAG, "Post请求String同步响应success==" + response.body().string());
} else {
Log.e(TAG, "Post请求String同步响应failure==" + response.body().string());
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Post请求String同步响应failure==" + e.getMessage());
}
}
}).start();
请求结果如下:
RequestBody的数据格式都要指定Content-Type,常见的有三种:
MediaType mediaType = MediaType.parse("image/png");
RequestBody requestBody = RequestBody.create(xxx.png, mediaType);
改变MediaType 中的内容即可设置不同的内容类型,比如image/png表示便携式网络图形(Portable Network Graphics,PNG)是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性。
(7)POST提交String请求(异步)
RequestBody requestBody = RequestBody.create("提交内容", MediaType.parse("text/plain; charset=utf-8"));
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post请求String异步响应failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String string = response.body().string();
Log.e(TAG, "Post请求String异步响应success==" + string);
}
});
异步请求主要是有回调方法,和其他部分和同步差不多,我们来看看效果:
(8)POST提交键值对请求(异步)
请求参数提交键值对需要用到FormBody,FormBody继承自RequestBody,通过.add("key", "value")形式添加:
//提交键值对需要用到FormBody,FormBody继承自RequestBody
FormBody formBody = new FormBody.Builder()
//添加键值对(通多Key-value的形式添加键值对参数)
.add("key", "value")
.build();
final Request request = new Request.Builder()
.post(formBody)
.url("url")
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post请求(键值对)异步响应failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post请求(键值对)异步响应Success==" + result);
}
});
(9)POST提交文件请求(异步)
post提交文件,将文件传入RequestBody中即可:
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), new File("text.txt"));
Request request = new Request.Builder()
.post(requestBody)
.url("https://api.github.com/markdown/raw")
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post请求(文件)异步响应failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post请求(文件)异步响应Success==" + result);
}
请求效果如下:源码中有例子
(10)POST提交表单请求(异步)
使用FormEncodingBuilder来构建和HTML标签相同效果的请求体,键值对将使用一种HTML兼容形式的URL编码来进行编码
//使用FormEncodingBuilder来构建和HTML标签相同效果的请求体,键值对将使用一种HTML兼容形式的URL编码来进行编码
FormBody 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();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post请求(表单)异步响应failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post请求(表单)异步响应Success==" + result);
}
});
(11)POST提交流请求(同步)
以流的方式Post提交请求体,请求体的内容由流写入产生,这里是流直接写入OKIO的BufferedSink。你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取,这里需要重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务器。
//以流的方式Post提交请求体,请求体的内容由流写入产生,这里是流直接写入OKIO的BufferedSink。
// 你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取
final MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
//重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务器
final RequestBody requestBody = new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
//返回内容类型
return mediaType;
}
@Override
public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
//输入数据头
bufferedSink.writeUtf8("Numbers\\n");
bufferedSink.writeUtf8("-------\\n");
//构造数据
for (int i = 2; i < 997; i++) {
bufferedSink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
};
//构建请求
final Request request = new Request.Builder().
url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
//开启线程
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
String result = response.body().toString();
Log.e(TAG, "Post请求(流)异步响应Success==" + result);
} else {
Log.e(TAG, "Post请求(流)异步响应failure==" + response);
throw new IOException("Unexpected code " + response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) {
return factor(x) + "x" + i;
}
}
return Integer.toString(n);
}
请求结果如下:
(12)POST提交分块请求(异步)
MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容,多块请求头中的每个请求体都是一个亲求体,可以定义自己的请求体,这些请求体可以用来描述这块请求,例如他的Content-Disposition,如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。
MediaType mediaType = MediaType.parse("image/png");
String IMGUR_CLIENT_ID = "...";
//构建body
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png", RequestBody.create(mediaType,
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(multipartBody)
.build();
//执行请求
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "Post请求(分块)异步响应failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String result = response.body().string();
Log.e(TAG, "Post请求(分块)异步响应Success==" + result);
}
});
请求结果如下:
如果你上传一个文件不是一张图片,但是MediaType.parse("image/png")里的"image/png"不知道该填什么,可以参考下这个页面。
源码地址
相关文章:
Retrofit2详解和简单使用
- Retrofit2的介绍和简单使用
OKHttp3的使用和详解
- OKHttp3的用法介绍和解析
OKHttp3源码详解
- 从源码角度解释OKHttp3的关键流程和重要操作
转载:https://blog.csdn.net/m0_37796683/article/details/101029208