Okhttp是目前主流网络框架之一,往往结合retrofit一起使用。因为现在项目主流框架是RxJava+Okhttp+Retrofit,所以我将Okhttp的讲解放在了Rx系列专题。
(1)添加依赖库
implementation 'com.squareup.okhttp3:okhttp:3.12.2'
(2)解决引入依赖库之后的异常
引入依赖库之后需要对Java8的编译支持,否则会报错,在build.gradle
中添加:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
(3)添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
(4)Get请求
- 同步调用
使用call.execute()实现同步调用,但是从Android3.0之后出现一个新的策略,凡是网络请求必须在异步线程执行,不能在UI线程执行,否则抛出异常,所以以下代码的网络请求部分必须在子线程执行
//(1)创建client对象
OkHttpClient client = new OkHttpClient.Builder().build();
//(2)创建Request对象
Request request = new Request.Builder()
.get()
.url("https:www.baidu.com")
.build();
//(3)将Request封装成Call
Call call = client.newCall(request);
new Thread(new Runnable() {
@Override
public void run() {
try {
//(4)开始Get请求
Response response = call.execute();
if(response.isSuccessful()){
}else{
}
Log.d("aaa", response.body().toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
在平时,我们应当尽量避免在UI线程执行网络请求,因为网络请求必然会阻塞UI线程。
其实call.execute()是可以在UI线程执行的,我们可以通过代码取消Android3.0对网络请求不能在UI线程执行
的限制
StrictMode.ThreadPolicy policy=new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
在onCreate
方法中添加这两句代码就可以让call.execute()在主线程执行。
//(1)创建client对象
OkHttpClient client = new OkHttpClient.Builder().build();
//(2)创建Request对象
Request request = new Request.Builder()
.get()
.url("https:www.baidu.com")
.build();
//(3)将Request封装成Call
Call call = client.newCall(request);
//(4)开始Get请求
Response response = null;
try {
response = call.execute();
if(response.isSuccessful()){
}else{
}
} catch (IOException e) {
e.printStackTrace();
}
Log.d("aaa", response.body().toString());
-
异步调用
//(1)创建client对象 OkHttpClient client = new OkHttpClient.Builder().build(); //(2)创建Request对象 Request request = new Request.Builder() .get() .url("https:www.baidu.com") .build(); //(3)将Request封装成Call Call call = client.newCall(request); //(4)异步调用,并设置回调函数 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, final Response response) throws IOException { final String res = response.body().string(); } });
异步调用onFailure
和onResponse
是在非UI线程上执行的。
(5)Post请求
//创建client对象
OkHttpClient client = new OkHttpClient();
//创建FormBody对象,并传参
FormBody formBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "admin")
.build();
//创建request对象
Request request = new Request.Builder()
.url("http://www.jianshu.com/")
.post(formBody)
.build();
//将Request封装成Call
Call call = client.newCall(request);
//开始网络请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("aaa", "11111111111111");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String res = response.body().string();
Log.d("aaa", "22222:"+res);
}
});
同步调用和Get请求一致。
(6)OkHttpClient
OkHttpClient可配置的属性有
OkHttpClient client=new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS) //设置连接超时
.readTimeout(60, TimeUnit.SECONDS) //设置读超时
.writeTimeout(60,TimeUnit.SECONDS) //设置写超时
.retryOnConnectionFailure(true) //是否自动重连
.build(); //构建OkHttpClient对象
内存的释放
client.dispatcher().executorService().shutdown(); //清除并关闭线程池
client.connectionPool().evictAll(); //清除并关闭连接池
client.cache().close(); //清除cache
newBuilder
的使用
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
这样创建的实例与原实例共享线程池、连接池和其他设置项,只需进行少量配置就可以实现特殊需求。
(7)Request
Request是网络请求的对象,其本身的构造函数是private的,只能通过Request.Builder来构建(建造者设计模式)。下面代码展示了常用的设置项。
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues") //设置访问url
.get() //类似的有post、delete、patch、head、put等方法,对应不同的网络请求方法
.header("User-Agent", "OkHttp Headers.java") //设置header
.addHeader("Accept", "application/json; q=0.5") //添加header
.removeHeader("User-Agent") //移除header
.headers(new Headers.Builder().add("User-Agent", "OkHttp Headers.java").build()) //移除原有所有header,并设置新header
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
(8)RequestBody
和FormBody
、 MultipartBody
- RequestBody
从上图可知,创建RequestBody请求体支持文件、byte数组以及字符串。
就以字符串为例
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("username", "admin");
jsonObject.put("password", "admin");
} catch (JSONException e) {
e.printStackTrace();
}
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toString());
另外,requestBody可以获取以下属性
contentLength:
消息体长度
isDuplex:
是否支持双工
isOneShot:
是否支持单工
contentType:
获取MediaType
这里还需要重点介绍一下MediaType
,可分为::
json:
application/json
xml:
application/xml
png:
image/png
jpg:
image/jpeg
gif:
imge/gif
form:
application/x-www-form-urlencoded
使用时最好带上编码:MediaType.parse("application/json; charset=utf-8")
- FormBody
FromBody用于提交表单键值对,其作用类似于HTML中的<form>标记。
FormBody是RequestBody的子类,从源码可以看出它默认MediaType是application/x-www-form-urlencoded
FormBody支持设置键值对
RequestBody formBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "admin")
.addEncoded(URLEncoder.encode("A"), URLEncoder.encode("B"))
.build();
FormBody 的两个方法add
和addEncoded
可以添加键值对。
FormBody对象可以获取到的值如图所示:
encodedName:
指定角标,获取已编码的key;
encodedValue:
指定角标,获取已编码的value;
name:
指定角标获取key;
value:
指定角标获取value;
contentLength:
获取请求体的长度;
contentType:
获取MediaType类型;
size:
获取键值对的数量
- MultipartBody
使用MultipartBody.Builder可以构建与HTML文件上传格式兼容的复杂请求体。多块请求体中每块请求都是一个独立的请求体,都可以定义自己的请求头。这些请求头应该用于描述对应的请求体,例如Content-Disposition,Content-Length,和Content-Type会自动被添加到请求头中。
它有五种MediaType,分别是:
MultipartBody.MIXED:
"multipart/mixed"
MultipartBody.ALTERNATIVE:
"multipart/alternative"
MultipartBody.DIGEST:
"multipart/digest"
MultipartBody.PARALLEL:
"multipart/parallel"
MultipartBody.FORM:
"multipart/form-data"
MultipartBody.FORM
比较常用,其他四种查询了很多资料也没有完全理清楚。
multipartBody可调用的方法有:
setType:
设置MediaType
addFormDataPart:
两个参数:键值对
三个参数:键值对+文件消息体(一般用于文件上传)
addPart:
每个Part(块)都会封装一个请求体
代码如下:
RequestBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MediaType.parse("image/png"), new File("website/static/logo.png")))
.addFormDataPart("discription","beautiful")
.build();
(9)Call
Call对象表示一个已经准备好可以执行的请求,用这个对象可以查询请求的执行状态,或者取消当前请求。它具有以下方法:
Call call=client.newCall(request); //获取Call对象
Response response=call.execute(); //同步执行网络请求,不要在主线程执行
call.enqueue(new Callback()); //异步执行网络请求
call.cancel(); //取消请求
call.isCanceled(); //查询是否取消
call.isExecuted(); //查询是否被执行过
要注意的是,每个Call对象只能执行一次请求。如果想重复执行相同的请求,可以:
Call reCall=client.newCall(call.request()); //获取另一个相同配置的Call对象
(10)Response
Response是网络请求的结果下面是一些常用方法:
Response response=call.execute(); //获取Response对象
response.code(); //请求的状态码
response.isSuccessful(); //如果状态码为[200..300),则表明请求成功
Headers headers=response.headers(); //获取响应头
List<String> names=response.headers("name"); //获取响应头中的某个字段
ResponseBody body=response.body(); //获取响应体
其中ResponseBody代表响应体,用于操作网络请求返回的内容。常用方法如下:
body.contentLength(); //body的长度
String content=body.string(); //以字符串形式解码body
byte[] byteContent=body.bytes(); //以字节数组形式解码body
InputStreamReader reader=body.charStream(); //将body以字符流的形式解码
InputStream inputStream=body.byteStream(); //将body以字节流的形式解码
需要注意的是:
-
ResponseBody必须关闭,不然可能造成资源泄漏,你可以通过以下方法关闭ResponseBody,对同一个ResponseBody只要关闭一次就可以了。
Response.close(); Response.body().close(); Response.body().source().close(); Response.body().charStream().close(); Response.body().byteString().close(); Response.body().bytes(); Response.body().string();
ResponseBody只能被消费一次,也就是string(),bytes(),byteStream()或 charStream()方法只能调用其中一个。
如果ResponseBody中的数据很大,则不应该使用bytes() 或 string()方法,它们会将结果一次性读入内存,而应该使用byteStream()或 charStream(),以流的方式读取数据。