一 、OKHttp简介
OKHttp是一个处理网络请求的开源项目,Android 当前最火热网络框架,由移动支付Square公司贡献,用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient)。
GitHub地址:https://github.com/square/okhttp
1、OKHttp优点
支持HTTP2/SPDY(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。)
socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数。
基于Headers的缓存策略减少重复的网络请求。
拥有Interceptors轻松处理请求与响应(自动处理GZip压缩)。
2、OKHttp的功能
PUT,DELETE,POST,GET等请求
文件的上传下载
加载图片(内部会图片大小自动压缩)
支持请求回调,直接返回对象、对象集合
支持session的保持
二 OkHttp3使用
首先要在AndroidManifest.xml里添加网络权限,如果需要上传或下载文件还需要添加存储卡读写权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
1、异步get请求
public void getAsyncRequest(){
//1、创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//2、创建Request对象,设置一个URL地址,设置请求方式
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
//3、创建一个call对象,参数为request请求对象
Call call = client.newCall(request);
//4、将请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功
if (response.isSuccessful()){
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}
});
}
发送一个异步GET请求的4个步骤:
(1)创建OkHttpClient对象
(2)通过Builder模式创建Request对象,参数必须有个url参数,可以通过Request.Builder设置更多的参数比如:header、method等
(3)通过request的对象去构造得到一个Call对象,Call对象有execute()和cancel()等方法。
(4)以异步的方式去执行请求,调用的是call.enqueue,将call加入调度队列,任务执行完成会在Callback中得到结果。
注意事项:
(1)Request.Builder中默认的使用Get请求,所以可以不调用get()方法
(2)无论是同步还是异步请求,接收到Response对象时均在子线程中,其中通过Response对象获取请求结果需要在子线程中完成,我们不能在子线程更新UI,需要借助于 runOnUiThread() 方法或者 Handler 来处理。
(3)onResponse回调有一个参数是response,如果我们想获得返回的是字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调response.body().byteStream(),有inputStream我们就可以通过IO的方式写文件。onResponse执行的线程并不是UI线程。
2、同步GET请求(同步GET请求和异步GET请求基本一样,不同地方是同步请求调用Call的execute()方法,而异步请求调用call.enqueue()方法)
public void getSyncRequest(){
//1、创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//2、创建request对象
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
//3、创建call对象
Call call = client.newCall(request);
//4、同步调用会阻塞主线程,这边在子线程进行
new Thread(new Runnable() {
@Override
public void run() {
try {
//同步调用,返回response,会抛出IO异常
Response response = call.execute();
if (response.isSuccessful()){
}
} catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
3、POST请求提交键值对
public void postAsyncRequest(){
//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//通过new FormBody()调用build方法创建RequestBody,用add()添加键值对
RequestBody requestBody = new FormBody.Builder()
.add("username","admin")
.add("password","123")
.build();
//设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
//创建call对象,参数就是Request请求对象
Call call = client.newCall(request);
//将请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功
if (response.isSuccessful()){
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}
});
}
一个异步POST请求提交键值对的5个步骤:
(1)创建OkHttpClient对象。
(2)通过new FormBody()调用build方法,创建一个RequestBody,可以用add添加键值对 ,FormBody 是 RequestBody 的子类。
(3)创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入。
(4)创建一个call对象,参数就是Request请求对象。
(5)请求加入调度,重写回调方法。
4、异步POST请求提交字符串(POST请求提交字符串和POST请求提交键值对非常相似,不同地方主要是RequestBody)
public void postAsyncRequest(){
//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
String value = "{username:admin;password:123}";
//通过RequestBody.create创建RequestBody对象
RequestBody requestBody = RequestBody.create(mediaType,value);
//设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
//创建call对象,参数就是Request请求对象
Call call = client.newCall(request);
//将请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功
if (response.isSuccessful()){
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}
});
}
5、异步POST请求上传文件
public void postAsyncRequest(){
//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/octet-stream");
File file = new File(Environment.getExternalStorageDirectory(),"1.png");
RequestBody requestBody = RequestBody.create(mediaType,file);
//设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
//创建call对象,参数就是Request请求对象
Call call = client.newCall(request);
//将请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功
if (response.isSuccessful()){
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}
});
}
RequestBody是一个抽象类,分别有FormBody和MultipartBody两个子类,上面这个例子使用的是FormBody,用于传输表单类型的参数。MultipartBody则支持多类型的参数传递,在传输表单类型的参数的同时,还是可以传输文件。
6、异步GET请求下载文件
//get请求异步下载图片
public void getPhotoAsyncRequest(){
//1、创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//2、创建Request对象,设置一个URL地址,设置请求方式
Request request = new Request.Builder()
.url("https://www.baidu.com/img/bd_logo1.png")
.get()
.build();
//3、创建一个call对象,参数为request请求对象
Call call = client.newCall(request);
//4、将请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功
if (response.isSuccessful()){
//请求成功
//拿到字节流
InputStream is = response.body().byteStream();
int len = 0;
//设置下载图片存储路径和名称
File file = new File(Environment.getExternalStorageDirectory(),"baidu.png");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[128];
while ((len = is.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.flush();
fos.close();
is.close();
// //下载图片并直接设置到ImageVeiw中
// InputStream is = response.body().byteStream();
// //使用BitmapFactory的decodeStream将图片的输入流直接转换为Bitmap
// final Bitmap bitmap = BitmapFactory.decodeStream(is);
// //在主线程中操作UI
// runOnUiThread(new Runnable() {
// @Override
// public void run() {
// //将Bitmap设置到ImageView中
// imageView.setImageBitmap(bitmap);
// }
// });
// is.close();
}
});
}
7、异步POST请求上传Multipart文件(在有些情况下既要上传文件还要上传其他类型字段。比如在个人中心我们可以修改名字,年龄,修改图像,这其实就是一个表单。这里我们用到MuiltipartBody ,它 是RequestBody 的一个子类,我们提交表单就是利用这个类来构建一个 RequestBody)
public void postAsyncRequest(){
//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("image/png");
File file = new File(Environment.getExternalStorageDirectory(),"1.png");
RequestBody fileRequestBody = RequestBody.create(mediaType,file);
RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("username","admin")
.addFormDataPart("age","25")
.addFormDataPart("image","1.png",fileRequestBody)
.build();
//设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
//创建call对象,参数就是Request请求对象
Call call = client.newCall(request);
//将请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功
if (response.isSuccessful()){
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}
});
}
注意事项
MultipartBody.Builder().addFormDataPart() 方法的第一个参数就是类似于键值对的键,是供服务端使用的,第二个参数是文件的本地的名字,第三个参数是 RequestBody,里面包含了我们要上传的文件的路径以及 MediaType。MultipartBody中可以添加一个或多个RequestBody对象及其他表单类型参数。
总结:
(1)当请求的RequestBody中只有表单类型参数时,使用FormBody.Builder()方法创建RequestBody对象
(2)当请求的RequestBody中是字符串或者文件时,使用RequestBody.create()方法创建RequestBody对象
(3)当请求的RequestBody中既有文件类型又有表单类型参数时,使用RequestBody.create()创建一个文件RequestBody对象,然后使用MultipartBody.Bulider()的addForDataPart()把文件RequestBody对象和其他表单类型参数添加进去,生成一个最终的RequestBody对象。
(4)无论post什么类型的数据,区别都在于创建RequestBody对象的方法
三、设置超时时间
OkHttp可以设置调用、连接和读写的超时时间,都是通过OkHttpClient.Builder设置的。如果不主动设置,OkHttp将使用默认的超时设置
OkHttpClient mClient = new OkHttpClient.Builder()
.callTimeout(6_000, TimeUnit.MILLISECONDS)
.connectTimeout(6_000, TimeUnit.MILLISECONDS)
.readTimeout(20_000, TimeUnit.MILLISECONDS)
.writeTimeout(20_000, TimeUnit.MILLISECONDS)
.build();
四、设置请求Header
请求的Header是通过Request.Builder对象的相关方法来维护的,如下:
headers(Headers headers)
header(String name, String value)
addHeader(String name, String value)
removeHeader(String name)
addHeader和removeHeader方法比较好理解,分别是添加和移除header信息。header(String name, String value)这是会重新设置指定name的header信息。
headers(Headers headers)则是会移除掉原有的所有header信息,将参数headers的header信息添加到请求中。这是这几个方法的一些差别。
Cookie也是header信息中的一个字段,通过Header相关方法添加就好了。
使用的话都是Builder模式的链式调用,举个栗子
Request request = new Request.Builder()
.header("Accept","image/webp")
.addHeader("Charset","UTF-8")
.url(url)
.build();
五、Interceptors(拦截器)
拦截器是OkHttp当中一个比较强大的机制,可以监视、重写和重试调用请求。
这是一个比较简单的Interceptor的实现,对请求的发送和响应进行了一些信息输出。
class LoggingInterceptor implements Interceptor {
public static final String TAG = "Http_log";
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.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()));
return response;
}
}
需要实现其中intercept(Interceptor.Chain chain)
方法,同时必须调用chain.proceed(request)
代码,也就是网络请求真正发生的地方。
拦截器可以设置多个,并且拦截器的调用是有顺序的。官网举的例子是,同时添加一个压缩拦截器和一个校验拦截器,需要决定数据是先被压缩在校验,还是先校验在压缩。
拦截器还分为应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors)
Application Interceptors
先看看应用拦截器,通过OkHttpClient.Builder的addInterceptor方法添加拦截器
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
看请求和响应的两个链接是不同的,URL http://www.publicobject.com/helloworld.txt会重定向到 https://publicobject.com/helloworld.txt,OkHttp会自动跟随重定向,而应用拦截器只被调用一次,并且chain.proceed()返回的Response对象是具有重定向响应对象。
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
Network Interceptors
再来看看网络拦截器,通过OkHttpClient.Builder的addNetworkInterceptor方法添加拦截器
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
结果日志:
INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
从日志来看,拦截器运行了两次,第一次请求了http://www.publicobject.com/helloworld.txt,第二次则是重定向到https://publicobject.com/helloworld.txt。同时通过网络拦截能获得更多的header信息
转自:https://blog.csdn.net/zhangqiluGrubby/article/details/71480546
https://www.jianshu.com/p/aaa87e8ad9eb